# Vue SSR 指南

本文基于 Vue 和 Nuxt.js 为基础进行编写。

# 什么是 SSR

在默认情况下,Vue 可以在浏览器中输出「组件」,进行生成 DOM 和操作 DOM;

当然,也可以将某个组件,在服务端渲染为 HTML 字符串,然后再将它发送给浏览器,最后通过静态标记,「激活」为客户端上,一个完全可交互的应用程序。

这就是 SSR(Server Side Rendering,服务端渲染);处于大部分代码可以在「服务器」和「客户端」上运行,所以经过服务端渲染处理的 Vue.js 应用程序,也可以被看做「同构」或者「通用」操作。

# 使用 SSR 的优劣

在使用 SSR 之前,你需要执行判断你是否真正需要它。

# 👍🏻 更好的 SEO

这样,搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

目前,Google 和 Bing 可以很好对「同步」的 JavaScript 应用程序进行索引。在这里,「同步」是关键。

如果你的应用程序初始展示 Loading 菊花图,然后通过 Ajax 异步获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染解决此问题。

# 👍🏻 更快的内容到达时间

内容到达时间(Time To Content),通常对于那些「内容到达时间(time-to-content) 与转化率直接相关」的应用程序而言,服务器端渲染 (SSR) 至关重要,这样可以产生更好的用户体验。

因为经过服务端渲染的应用程序,无需等待所有 JavaScript 都下载完成并执行,特别是对于缓慢的网络情况或运行缓慢的设备,用户将会更快速地看到完整渲染的页面。

# 👎🏻 开发条件受限

浏览器特定的代码,只能在某些生命周期钩子函数(lifecycle hook)中使用;一些外部扩展库(external library)可能需要特殊处理,才能在服务器渲染应用程序中运行。

# 👎🏻 部署要求更高

与可以部署在任何静态文件服务器上的完全静态单页面应用程序(SPA)不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。

# 👎🏻 服务器负载增大

在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源(CPU-intensive,CPU 密集),因此如果你预料在高流量环境(high traffic)下使用,请准备相应的服务器负载,并明智地采用缓存策略。

# 通用代码

至于什么是通用代码,我们需要明白一个约束:

我们的代码将会运行在「服务器」和「客户端」,正是因为目标平台 API 存在差异,在运行环境不同的情况下,我们的代码不能完全相同。

对于服务端渲染,我们希望每个请求获取到的应该是全新、独立的应用程序,不应该存在交叉请求造成的状态污染(cross-request state pollution)。

# 交叉请求状态污染

为了防止交叉请求造成的状态污染,我们需要避免状态单例。

由于 Node.js 服务器是一个长期运行的进程。当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享。

因此我们要为每个请求创建一个新的根 Vue 实例,而不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例,代码如下:

// app.js
// 工厂函数用于创建应用程序实例,可重复执行
const Vue = require('vue')

module.exports = function createApp (context) {
  return new Vue({
    data: {
      url: context.url
    },
    template: `<div>访问的 URL 是: {{ url }}</div>`
  })
}

// server.js
const createApp = require('./app')

server.get('*', (req, res) => {
  const context = { url: req.url }
  const app = createApp(context)

  renderer.renderToString(app, (err, html) => {
    // 处理错误……
    res.end(html)
  })
})

同样的规则也适用于 router、store 和 event bus 实例。

你不应该直接从模块导出并将其导入到应用程序中,而是需要在 createApp 中创建一个新的实例的时候,从根 Vue 实例注入。

在使用带有 { runInNewContext: true } 的 bundle renderer 时,可以消除此约束;

但是由于需要为每个请求创建一个新的 vm 上下文,因此伴随有一些显著性能开销。

# 服务器和客户端差异

由于在渲染的过程需要确定性,我们需要在开始渲染的时候,应用程序就完成了数据的状态解析,见数据预取;

这样一来,在服务器上进行数据的响应式化操作,明显是多余的,所以默认情况下 🚫禁用了「响应式数据」;

由于缺少了动态更新,服务端也无法使用除了 beforeCreate 和 created 以外的生命周期 HOOk;像 beforeMount 或 mounted 这种看上去就只会在客户端执行的 HOOK,在服务器上则无法执行;

动态更新

这里的动态更新,指的是:

此外,一些访问特定平台(Platform-Specific)API 的代码,是不能被用在通用代码中的,例如 window 和 document 这种仅浏览器可用的全局变量,在 Node.js 环境中执行的时候,则会抛出错误,反之亦是。

# 数据预取

# 预渲染

# 参考资料

Vue.js 服务器端渲染指南 | Vue SSR 指南 “服务端渲染”吊打“客户端渲染”的那些事

上次更新: 2020/4/10下午8:48:25