vue SSR 服务端渲染记录

前几天了解了下vue 服务端渲染的流程,记录下。首先,什么是ssr(服务端渲染 Server Side Rendering),为什么需要?

服务端渲染是什么

前后端分离之后,页面加载的流程是,前端异步请求拿到数据渲染页面。服务端渲染就是在后端把数据取好,拼好页面的DOM树发给前端,到浏览器解析渲染。有没有想到前后端分离之前,由后端把数据塞进模版,前端负责显示的过去。(有没有种天下之势,合久必分,分久必合的感慨哈哈哈哈哈)

服务端渲染优点

  • 页面的SEO, 异步拿数据显示对爬虫不友好
  • 首屏渲染速度快,更好的用户体验

服务端渲染原理

接下来,介绍下vue 服务端实现原理及流程。
vue SSR流程图

  1. SSR 有两个入口文件client-entry,server-entry , webpack打包之后,生成 server-bundle, client-bundle
  2. 服务器收到浏览器的请求,创建一个bundleRenderer,读取1生成的server-bundle,执行代码(具体做了什么后面会讲到),生成html发送到前端
  3. 把第二步生成的html跟前端的client-bundle进行混合(hydrate),混合时判断client-bundle 的DOM节点跟服务端返回的html里DOM节点是否相同,是的话挂载(vue中的$mount)到这个节点上,页面渲染完毕

用白话形容,服务端获取页面所需的数据之后,拼出html,把html转成string发送到前端,前端把html插入到指定节点,渲染页面,OK了。
表情

服务端数据预取

看看官网的demo,服务端怎么做的服务端数据预取。

// entry-server.js
import { createApp } from './app'
export default context => {
  return new Promise((resolve, reject) => {
    const { app, router, store } = createApp()
    router.push(context.url)
     // 等到 router 将可能的异步组件和钩子函数解析完
    router.onReady(() => {
      //获取相匹配的组件
      const matchedComponents = router.getMatchedComponents()
      if (!matchedComponents.length) {
        return reject({ code: 404 })
      }
      // 对所有匹配的路由组件调用 `asyncData()`
      Promise.all(matchedComponents.map(Component => {
        if (Component.asyncData) {
          return Component.asyncData({
            store,
            route: router.currentRoute
          })
        }
      })).then(() => {
        // 在所有预取钩子(preFetch hook) resolve 后,
        // 我们的 store 现在已经填充入渲染应用程序所需的状态。
        // 当我们将状态附加到上下文,
        // 并且 `template` 选项用于 renderer 时,
        // 状态将自动序列化为 `window.__INITIAL_STATE__`,并注入 HTML。
        context.state = store.state
        resolve(app)
      }).catch(reject)
    }, reject)
  })
}
  1. 根据router拿出相匹配的组件,客户端定义asyncData(数据预取函数,拿数据),服务端asyncData,获取数据
  2. 把源数据和状态写进store(数据和状态存储容器,store独立于业务组件,详情可查看Vuex),避免客户端和服务端状态不对等。状态写进window.__INITIAL_STATE__格式,客户端可拿到

bundleRenderder

html渲染好之后,转成string发到客户端,客户端插入到对应DOM节点下就可以啦~

const { createBundleRenderer } = require('vue-server-renderer')
const renderer = createBundleRenderer(serverBundle, {
  runInNewContext: false, // 推荐
  template, // (可选)页面模板
  clientManifest // (可选)客户端构建 manifest
})
// 在服务器处理函数中……
server.get('*', (req, res) => {
  const context = { url: req.url }
  // 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。
  // 现在我们的服务器与应用程序已经解耦!
  renderer.renderToString(context, (err, html) => {
    // 处理异常……
    res.end(html)
  })
}

服务端渲染一些坑

  • document 对象找不到,由于前端使用的 window,在 node 环境不存在
  • 数据预获取时,组件尚未实例化(无法使用 this ),数据请求及格式化等操作都应该放置在store处理
阅读 5.5k

推荐阅读