【前端】服务端渲染

8/7/2022 performance

# 概述

  • 页面渲染流程:

    • 浏览器通过请求得到一个 HTML 文本
    • 渲染进程解析 HTML 文本,构建 DOM 树
    • 解析 HTML 的同时,如果遇到内联样式或者样式脚本,则下载并构建样式规则(stytle rules),若遇到 JS 脚本,则会下载执行脚本
    • DOM 树和样式规则构建完成之后,渲染进程将两者合并成渲染树(render tree)
    • 渲染进程开始对渲染树进行布局,生成布局树(layout tree)
    • 渲染进程对布局树进行绘制,生成绘制记录
    • 渲染进程的对布局树进行分层,分别栅格化每一层,并得到合成帧
    • 渲染进程将合成帧信息发送给 GPU 进程显示到页面中
  • 客户端渲染(Client Side Render,CSR):在执行 JS 脚本的时候,HTML 页面已经开始解析并且构建 DOM 树了,JS 脚本只是动态的改变 DOM 树的结构,使得页面成为希望成为的样子。

  • 服务端渲染(Server Side Render,SSR):在浏览器请求页面 URL 的时候,服务端将需要的 HTML 文本组装好,并返回给浏览器,这个 HTML 文本被浏览器解析之后,不需要经过 JS 脚本的执行,即可直接构建出希望的 DOM 树并展示到页面中。

# 发展过程

  • Web1.0
    • 在没有 AJAX 的时候,也就是 web1.0 时代,几乎所有应用都是服务端渲染(此时服务器渲染非现在的服务器渲染),那个时候的页面渲染大概是这样的:浏览器请求页面 URL,然后服务器接收到请求之后,到数据库查询数据,将数据丢到后端的组件模板(php、asp、jsp 等)中,并渲染成 HTML 片段,接着服务器在组装这些 HTML 片段,组成一个完整的 HTML,最后返回给浏览器,这个时候,浏览器已经拿到了一个完整的被服务器动态组装出来的 HTML 文本,然后将 HTML 渲染到页面中,过程没有任何 JavaScript 代码的参与。但是随着业务的日益复杂和后续 AJAX 的出现,也渐渐开始暴露出了 WEB1.0 服务器渲染的缺点。
    • 不足:每次更新页面的一小的模块,都需要重新请求一次页面,重新查一次数据库,重新组装一次 HTML;前端 JavaScript 代码和后端(jsp、php、jsp)代码混杂在一起,使得日益复杂的 WEB 应用难以维护。
  • 客户端渲染(CSR)
    • 随之 nodejs 的出现,前端看到了翻身的契机,为了摆脱后端的指指点点,前端开启了一场前后端分离的运动,希望可以脱离后端独立发展。前后端分离之后,网页开始被当成了独立的应用程序(SPA,Single Page Application),前端团队接管了所有页面渲染的事,后端团队只负责提供所有数据查询与处理的 API。
    • 基本流程:首先浏览器请求 URL,前端服务器直接返回一个空的静态 HTML 文件(不需要任何查数据库和模板组装),这个 HTML 文件中加载了很多渲染页面需要的 JavaScript 脚本和 CSS 样式表,浏览器拿到 HTML 文件后开始加载脚本和样式表,并且执行脚本,这个时候脚本请求后端服务提供的 API,获取数据,获取完成后将数据通过 JavaScript 脚本动态的将数据渲染到页面中,完成页面显示。
  • 服务端渲染(SSR)
    • 随着单页应用(SPA)的发展,程序员们渐渐发现 SEO(Search Engine Optimazition,即搜索引擎优化)出了问题,而且随着应用的复杂化,JavaScript 脚本也不断的臃肿起来,使得首屏渲染相比于 Web1.0 时候的服务端渲染,也慢了不少。于是,前端团队选择了使用 nodejs 在服务器进行页面的渲染,进而再次出现了服务端渲染。
    • 基本流程:首先浏览器请求 URL,前端服务器接收到 URL 请求之后,根据不同的 URL,前端服务器向后端服务器请求数据,请求完成后,前端服务器会组装一个携带了具体数据的 HTML 文本,并且返回给浏览器,浏览器得到 HTML 之后开始渲染页面,同时,浏览器加载并执行 JavaScript 脚本,给页面上的元素绑定事件,让页面变得可交互,当用户与浏览器页面进行交互,如跳转到下一个页面时,浏览器会执行 JavaScript 脚本,向后端服务器请求数据,获取完数据之后再次执行 JavaScript 代码动态渲染页面。

# 服务端渲染利弊

  • 优势
    • 利于 SEO:使用 SEO 有助于爬虫爬取网页,当用户使用搜索引擎查找相关内容时,网页排行可以更靠前。然而,爬虫又分为低级爬虫和高级爬虫。因此,使用服务端渲染对这些低级爬虫更加友好。
      • 低级爬虫:只爬取请求 URL 返回的纯 HTML 文本内容
      • 高级爬虫:在爬取返回的纯 HTML 文本之后,还会加载并执行 JavaScript 脚本渲染页面,最后对渲染后的页面内容进行爬取。
    • 白屏时间更短
      • 服务端渲染在浏览器请求 URL 之后已经得到了一个带有数据的 HTML 文本,浏览器只需要解析 HTML,就可以直接构建 DOM 树并显示。
      • 客户端渲染需要先得到一个空的 HTML 页面,这个时候页面已经进入白屏,之后还需要经过加载并执行 JavaScript、请求后端服务器获取数据、JavaScript 渲染页面几个过程才可以看到最后的页面。特别是在复杂应用中,由于需要加载 JavaScript 脚本,越是复杂的应用,需要加载的 JavaScript 脚本就越多、越大,这会导致应用的首屏加载时间非常长,进而降低了体验感。
  • 局限
    • 代码复杂度增加。为了实现服务端渲染,应用代码中需要兼容服务端和客户端两种运行情况,而一部分依赖的外部扩展库却只能在客户端运行,需要对其进行特殊处理,才能在服务器渲染应用程序中运行。
    • 更多的服务器负载均衡。由于服务器增加了渲染 HTML 的需求,使得原本只需要输出静态资源文件的 nodejs 服务,新增了数据获取的 IO 和渲染 HTML 的 CPU 占用,如果流量突然暴增,有可能导致服务器宕机,因此需要使用响应的缓存策略和准备相应的服务器负载。
    • 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js 服务器运行环境。
  • 使用 SSR 的综合考量(投入产出比):是否需要 SEO,首屏渲染时间是否真的很慢。

# 代码同构

  • 服务端渲染的方式有两种:一种在服务端组装 html 页面,另一种就是在客户端组装 html 页面,运行环境不一样。所谓组装就是当 JS 程序向后端重发一个数据请求后,将返回的数据和 html 文本进行封装。
  • 代码同构:让一份代码,既可以在服务端中执行,也可以在客户端中执行,并且执行的效果都是一样的,都是完成这个 html 的组装,正确显示页面。
  • 场景假设:使用 Vue.js 编写的单页应用
    • 打开某个页面的 URL,首先通过应用的路由找到对应的页面,不同的页面有不同的视图(视图 = 模板 + 数据)
    • 在 Vue 应用中,视图 = 组件 + 响应式数据模型
    • 即实现客户端与服务端的路由、模型组件、数据模型的共享。

# 实践

# 参考

Last Updated: 10/8/2022, 2:29:52 PM