前端技术发展到今天,用户通过终端浏览到应用内容的方式一般包含两种:
- 客户端渲染(CSR):客户端请求数据及 JS 内容,在客户端渲染成标准的页面呈现出来
- 服务端渲染(SSR):服务端从本地拿到数据,然后先渲染好页面再发送到客户端呈现给用户
Vue.js 默认在客户端渲染页面,但是我们可以通过 vue-server-renderer 实现服务端渲染。 用流程图的方式描述二则的区别如下:
CSR 与 SSR 是页面渲染的两种方式,二则各有利弊:
客户端渲染(CSR:Client Side Render)
客户端渲染需要先从服务器拿到数据(主要是些 JS 文件),等这些必要的文件全部加载完后才会渲染页面,比如 Vue.js 中会先拿到一个简单的 html 文件,然后获取必要的 js 文件,通过 js 文件生成虚拟 DOM 用来展示页面。因此客户端渲染往往具有如下特点:
- 初始加载慢,白屏时间长
- 无 DOM 结构,不利于 SEO
- 利于前后端分离,前端专注页面实现,后端专注于 API 开发
- 用户体验好, 方便应用交互
- 服务器压力小,页面渲染工作在客户端完成
服务端渲染(SSR:Server Side Render)
服务端渲染一般从本地拿到数据,根据数据渲染成前端可用的 html 页面,将页面发送给客户端快速显示,因此服务端渲染往往具有如下特点:
- 服务器压力大,特别是对 Vue.js 需要根据客户请求创建独立的 vue 实例,防止数据污染
- 用户发生交互行为后可能会频繁请求服务器
- 不利于前后端分离,开发效率低,开发受限
- 有利于 SEO,方便爬虫抓取数据
- 首屏加载迅速,避免长时间白屏造成用户体验不好
- 后端可生成静态缓存文件,避免大量数据库查询
先放两张图,对比下客户端渲染与服务端渲染,浏览器请求信息中的差异:
Vue 代码改造
Vue.js 是构建客户端应用程序的框架。默认情况下创建的应用程序是在浏览器中渲染并生成 DOM 和操作 DOM 的。也就是说,默认的 Vue 应用是客户端渲染(CSR)的,如果我们想让其在服务端渲染(SSR)就需要对其进行简单的改造。
为了避免服务端渲染造成的数据污染,我们需要通过工厂函数为每一个发起请求的用户创建独立的路由、状态管理、Vue实例。
createRouter
客户端渲染方式创建路由:
服务端渲染创建路由:
createStore
客户端渲染方式创建状态管理器:
服务端渲染方式创建状态管理器:
createApp
创建一个公共的文件 app.js ,方便客户端及服务端分别编译时调用。注意这里为方便区分没有直接改造 main.js。
客户端渲染方式创建 Vue 实例:
服务端渲染方式创建 Vue 实例:
entry-client.js
客户端编译时的入口文件,引入公共文件 app.js 中的代码后挂载实例。
注意:需要在 router.onReady 后挂载实例。
entry-server.js
服务端编译时的入口文件,引入公共文件 app.js 中的代码后
vue.config.js 改造
服务器端打包 JSON 数据,客户端打包静态文件,用于挂载实例。改造代码如下:
package.json 改造
将 package.json 中的 scripts 内容改成如下内容,这样就可以直接运行 npm run build 打包程序了。
服务器端渲染
要使用 vue-server-renderer 在服务器端渲染页面,我们需要先对其进行部署,这里以 node.js 服务器为例,使用 express 快速搭建服务器。
安装 vue-server-renderer
注意:vue-server-renderer 和 vue 必须匹配版本,且需要 node.js 环境支持
API 参考
因为之后的部署过程中可能用到相关 API ,这里先简单介绍一下。
- createRenderer:使用(可选的)选项创建一个 Renderer 实例
- createBundleRenderer:使用 server bundle 和(可选的)选项创建一个 BundleRenderer 实例
- Class: Renderer
- renderer.renderToString:将 Vue 实例渲染为字符串。上下文对象(context)可选。回调函数是的第一个参数是可能抛出的错误,第二个参数是渲染完毕的字符串。2.5.0+中,回调可选,默认返回 Promise 对象。
- renderer.renderToStream:将 Vue 实例渲染为一个 Node.js 可读流。上下文对象(context)可选。
- Class: BundleRenderer
- bundleRenderer.renderToString:将 bundle 渲染为字符串。上下文对象(context)可选。回调的第一个参数是可能抛出的错误,第二个参数是渲染完毕的字符串。2.5.0+中,回调可选,默认返回 Promise 对象。
- bundleRenderer.renderToStream:将 bundle 渲染为一个 Node.js 可读流。上下文对象(context)可选。
- Renderer 选项
- template:为整个页面的 HTML 提供一个模板。
- clientManifest:通过此选项提供一个由 vue-server-renderer/client-plugin 生成的客户端构建 manifest 对象。
- inject:控制使用 template 时是否执行自动注入。
- shouldPreload:一个函数,用来控制什么文件应该生成 <link rel=”preload”> 资源预加载提示 (resource hints)。
- shouldPrefetch:一个函数,用来控制对于哪些文件,是需要生成 <link rel=”prefetch”> 资源提示。
- runInNewContext:只用于 createBundleRenderer,是否每次渲染都创建一个新的 V8 上下文并重新执行整个 bundle。
- basedir:只用于 createBundleRenderer,显式地声明 server bundle 的运行目录。运行时将会以此目录为基准来解析 node_modules 中的依赖模块。
- cache:提供组件缓存具体实现。
- directives:对于自定义指令,允许提供服务器端实现。
server/index.js 代码
npm 安装