请注意,本文编写于 446 天前,最后修改于 446 天前,其中某些信息可能已经过时。
目录
为什么需要SSR(服务端渲染)
服务端渲染逻辑分析
VueSSR项目开发的主要工作
如何书写通用代码
SSR构建原理
常见问题
整理了下N年前我写的VueSSR学习笔记
官方文档
为什么需要SSR(服务端渲染)
- 主要是为了解决搜索引擎爬虫的问题
传统SPA返回的html的body是空dom和一个script引入,爬虫不会等待ajax响应完成填充dom,想要让seo知道你的东西就需要服务端返回完整的html,这就是ssr的用途
- 它可以提高首屏体验
服务端渲染逻辑分析
- 服务端渲染SSR应用与单页应用SPA仅仅是首屏不一样,
- SSR应用首屏需要在服务端生成完整的DOM结构,
- 之后的逻辑页面跳转、Store、Ajax走的都是客户端,与SPA完全相同。
- 尽管只是首屏一个页面在服务端渲染,但所有页面都可能是首屏,需要处理服务端渲染逻辑。
首屏渲染如何实现呢?
- 服务端需有一套与客户端相同的机制来处理Ajax请求和模版渲染
- 服务端请求数据后,对模版进行渲染生成html
- 然后将html和数据发送到客户端
- 客户端拿到数据对模版进行校验,并施加交互效果, 这一过程被称为注水
服务端渲染与客户端渲染有哪些区别呢?
- 首先是服务端环境与客户端环境的区别
- 服务端的全局对象是global, 客户端是 window; document, location, setTimeout, 事件监听等在服务端不能运行
- 服务端要根据请求重新生成版本,而客户端天然是多个,不需要考虑该问题
- 生命周期:
data、 beforeCreate、created会在服务端执行, beforeMount、mounted只在客户端执行
Vue官方是如何处理客户端与服务端渲染的差异呢?
官方提供了一个asyncData的钩子
- 如果是首屏渲染,在服务端执行asyncData函数请求数据,然后调用render进行渲染,并将数据挂在到
window.__INITIAL_STATE__对象上,客户端拿到数据后不再执行asyncData函数,而是从该对象取数据。在执行beforeCreate、created等钩子。
- 如果不是首屏,在客户端进行渲染,官方脚手架在路由钩子beforeEach进行拦截,执行asyncData钩子,然后在运行之后的生命周期,完成页面渲染。
VueSSR项目开发的主要工作
asyncData的实现。asyncData并不是vue实际的生命周期之一,而是官方项目提供的一种优雅解决服务端渲染问题的方案,会在所有vue生命周期之前调用且在异步(ajax)执行完成后再执行vue的其他生命周期函数,其原理详见entry-client.js, entry-server.js。
- 避免有状态的单例模式。 试想一下,SPA应用js运行在客户端。js运行产生的数据在不同用户的手机上互不影响。而ssr渲染产生的数据在服务器,如果有单例模式或缓存那么不同用户访问到的数据就一样了,更新也是个问题了(不同时间段返回的内容一样了)。所以前端路由每次请求服务器都要重新生成一份
store、router等继而在渲染页面。
这里有一个N年前我写的VueSSR的脚手架,内置了一些基本的功能。
如何书写通用代码
- 用store存储数据,ajax请求写在asyncData里面
- 首屏的数据挂在 window.initStatus 对象上,到了客户端直接读取这个对象获得首屏数据,这样就首页数据就不需要重复请求了
- 生命周期
- 服务端不能用window, document, location, navigator 其全局对象是global; 客户端不能出现仅服务端才能运行的代码 如 global
- data、 beforeCreate、created会在服务端执行, beforeMount、mounted只在客户端执行
- 服务端会运行的代码最好不要写定时器
- 服务端渲染不支持指令,推荐使用组件这种抽象机制
SSR构建原理

由于环境差异问题(服务端使用node进行渲染,客户端是浏览器渲染),客户端与服务端要单独进行打包
- 如table标签到客户端会动态插入tbody标签
- 服务端不需要解析CSS
- 服务端与客户端模块加载机制不同 commonjs/es modules
- 数据的响应在服务端不是必要的
- ajax请求,(当然axios已经做了)
具体实现
综合上述问题,所以构建的想法为:
- 使用 webpack 同时打包客户端和服务端应用。
- 服务端的包会被引入到服务端用来渲染 HTML,
- 同时客户端的包会被送到浏览器用于激活静态标记
具体实现关键节点、
- 分别指定客户端和服务端构建入口
- 定义一个变量指明工作环境
- 构建配置如编译目标设置
- 分别生成manifest。服务端渲染完成后根据客户端manifest推断并向渲染出来的 HTML 中注入 preload / prefetch 指令、
<link> 和 <script> 标签。
- 路由和代码分离, 服务端每次createRouter,按需加载
客户端激活
Vue 会断言客户端生成的虚拟 DOM 树与从服务器渲染出来的 DOM 结构是相匹配的。如果不匹配,则它会放弃激活的过程,抛弃已生成的 DOM 并从头开始渲染。这会在浏览器控制台产生一个警告,但网站还是能正常工作。
这里其实也是出于性能优化的考虑,除了避免DOM重新渲染,首屏数据也要避免客户端重复请求。
具体实现是需要服务端渲染的数据放在store里面,服务端通过注入JS对象,客户端运行进而接收。
常见问题
Q:只想对页面的部分内容进行服务端渲染怎么办?
A:ssr渲染的内容放在 里面,将其放在合适位置即可
Q: 可以某部分页面(如个人中心)不走服务端渲染吗?
A: 将ajax请求放在只会在客户端运行的生命周期即可。如beforeMount、mounted
Q: SSR与预渲染的区别
A: 预渲染构建后生成静态的HTML,而ssr是运行时构建动态的HTML.
本文作者:郭郭同学
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA
许可协议。转载请注明出处!