vue-ssr服务端渲染透析

这是我参与更文挑战的第29天,活动详情查看: 更文挑战

vue-ssr服务端渲染透析

背景
  • spa单页面seo不友好,因为vue的话是只有一个HTML页面,实现页面的切换是通过监听router进行路由分发,结合ajax加载数据进行渲染的,但是搜索引擎爬虫识别不了js,所以就不会有一个好的排名。但是通常情况下移动端不需要做seo优化,甲方这么要求的,说现在市面上也有实现了,这么也顺便实战一下。

什么是ssr(Server-Side Rendering服务端渲染)

  • 简单理解是将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记”混合”为客户端上完全交互的应用程序

  • Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,ssr也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记”激活”为客户端上完全可交互的应用程序。

  • 服务器渲染的 Vue.js 应用程序也可以被认为是”同构”或”通用”,因为应用程序的大部分代码都可以在服务器和客户端上运行。所谓同构,通俗的讲,就是一套 vue 代码在服务端上运行一遍,到达浏览器客户端又运行一遍。在服务端vue组件渲染为html字符串(即页面结构),在客户端操作dom。

  • 打开浏览器的network,我们看到了初始化渲染的HTML,并且是我们想要初始化的结构,且完全不依赖于客户端的js文件了。再仔细研究研究,里面有初始化的dom结构,有css,还有一个script标签。script标签里把我们在服务端entry拿到的数据挂载了window上。原来只是一个纯静态的HTML页面啊,没有任何的交互逻辑,所以啊,现在知道为啥子需要服务端跑一个vue客户端再跑一个vue了,服务端的vue只是混入了个数据渲染了个静态页面,客户端的vue才是去实现交互的!

ssr优缺点
优点
  • 更好的SEO

因为SPA页面的内容是通过Ajax获取,而搜索引擎爬取工具并不会等待Ajax异步完成后再抓取页面内容,所以在SPA中是抓取不到页面通过Ajax获取到的内容的;而SSR是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;

  • 更利于首屏渲染

首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。

缺点和约束

  • 由于没有动态更新,所有的生命周期钩子函数中,只有 beforeCreate 和 created 会在服务器端渲染 (SSR) 过程中被调用。这就是说任何其他生命周期钩子函数中的代码(例如 beforeMount 或 mounted),只会在客户端执行。
  • 如有在beforeCreat与created钩子中使用第三方的API,需要确保该类API在node端运行时不会出现错误,比如在created钩子中初始化一个数据请求的操作,这是正常并且及其合理的做法。但如果只单纯的使用XHR去操作,那在node端渲染时就出现问题了,所以应该采取axios这种浏览器端与服务器端都支持的第三方库。
  • 当编写纯客户端 (client-only) 代码时,我们习惯于每次在新的上下文中对代码进行取值。但是,Node.js 服务器是一个长期运行的进程。当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享。所以对于服务器端渲染,我们也希望每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染 (cross-request state pollution)。所以应该使用工厂函数来确保每个请求之间的独立性

ssr原理

根据应用的触发时机我们分成以下几个步骤详细讲解SSR是如何运作的:
编译阶段
vue-ssr提供的方式是配置两个入口文件(entry-client.js、entry-server.js),通过webpack把你的代码编译成两个bundle。

  • Server Bundle为vue-ssr-server-bundle.json:
  • Client Bundle为vue-ssr-client-manifest.json

初始化(获取到vue-ssr-server-bundle.json):

  • ssr应用会在node启动时初始化一个renderer单例对象,renderer对象由vue-server-renderer库的createBundleRenderer函数创建,函数接受两个参数,serverBundle(服务端入口文件打包后的)内容和options配置
  • 获取到serverBundle的入口文件代码并解析为入口函数,每次执行实例化vue对象
  • 实例化了render和templateRenderer对象,负责渲染vue组件和组装html

渲染阶段:(执行vue-ssr-server-bundle.json)

  • 当用户请求达到node端时,调用bundleRenderer.renderToString函数并传入用户上下文context,context对象可以包含一些服务端的信息,比如:url、ua等等,也可以包含一些用户信息。通过执行serverBundle后得到的应用入口函数,实例化vue对象。
  • renderer对象负责把vue对象递归转为vnode,并把vnode根据不同node类型调用不同渲染函数最终组装为html。
  • 当使用 template 时,context.state 将作为 window.INITIAL_STATE 状态,自动嵌入到最终的 HTML 中返回给客户端。而在客户端,在挂载到应用程序之前,store 就应该获取到状态:

内容输出阶段:

  • 在上一个阶段我们已经拿到了vue组件渲染结果,它是一个html字符串,在浏览器中展示页面我们还需要css、js等依赖资源的引入标签和我们在服务端的渲染数据,这些最终组装成一个完整的html报文输出到浏览器中。

客户端阶段(获取执行客户端代码 vue-ssr-client-manifest.json):

  • 当客户端发起了请求,服务端返回渲染结果和css加载完毕后,用户就已经可以看到页面渲染结果了,不用等待js加载和执行。服务端输出的数据有两种,一个是服务端渲染的页面结果,还有一个在服务端需要输出到浏览器的数据状态window.INITIAL_STATE
  • 这些数据需要同步给浏览器,否则会造成两端组件状态不一致。我们一般会使用vuex来存储这些数据状态,之前在服务端渲染完成后把vuex的state复制给用户上下文的context.state。context.state = store.state
  • 当客户端开始执行js时,我们可以通过window全局变量读取到这里的数据状态,并替换到自己的数据状态 store.replaceState(window.INITIAL_STATE);实现服务端和客户端的 store 数据同步
  • 之后在我们调用$mount挂载vue对象之前,客户端会和服务端生成的DOM进行Hydration对比(判断这个DOM和自己即将生成的DOM是否相同(vuex store 数据同步才能保持一致)
  • 如果相同就调用app.$mount(‘#app’)将客户端的vue实例挂载到这个DOM上,即去“激活”这些服务端渲染的HTML之后,其变成了由Vue动态管理的DOM,以便响应后续数据的变化,即之后所有的交互和vue-router不同页面之间的跳转将全部在浏览器端运行。
  • 如果客户端构建的虚拟 DOM 树vDOM与服务器渲染返回的HTML结构不一致,这时候,客户端会请求一次服务器再渲染整个应用程序,这使得ssr失效了,达不到服务端渲染的目的了。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享