Skip to content

页面的渲染模式 ​

渲染模式没有银弹。这篇文章大致写了前端的渲染模式有哪些,以及他们各自所长。

导言 ​

渲染模式的术语 ​

  • SSR (or with hydration)
  • CSR
  • SSG (Pre-rendering)
  • Trisomorphic Rendering()

性能衡量的术语 ​

  • TTFB: Time to First Byte - 被视为点击链接和第一部分内容之间的时间。

  • FP: First Paint - 任何像素第一次对用户可见的时间。

  • FCP: First Contentful Paint - 请求的内容(文章正文等)变得可见的时间。

  • TTI: Time To Interactive - 页面变得可交互的时间(连接的事件等)。

  • SEO

纯 SSR ​

纯的 SSR 是传统的老旧页面的响应方式的叫法。它指的是,用户请求一个页面,服务器就返回一个完整的页面,包含了 html, css, js,不同的路由都是如此。这种传统的服务页面的方法叫做多页应用程序。

CSR 与传统 SPA ​

单页应用(SPA)在用户请求一个网站的任何页面的时候,始终返回唯一的 html 文件,并且该 html 是空文件,只负责引入一个根 js 文件。该 js 文件即为单页应用的核心,它负责了所有页面的渲染,包括请求剩下的 css 和 js,并创建 DOM 以生成页面。根据导航栏的地址渲染出不同的页面,因为始终返回的都是同一个 html,页面的所有元素都是该 js 渲染的结果,所以称为“客户端渲染的单页应用”。

优缺点与性能评估 ​

传统的 SSR 和 CSR 的 SPA 其实是两个极端。SSR 将完整的页面(它包括了最终运行态的所需的 html, css, js)在服务器上准备好再丢给浏览器;而 CSR 则是仅仅提供一个包含如何渲染的逻辑的 js 文件,让真实的页面创建、路由页面切换、交互都在浏览器端运行。

传统的 SSR 的优势体现在一开始的时候。SSR 由于整体渲染时间只需要考虑当前页面的部分,并且不需要太多轮次的网络传输,所以相对而言首屏加载快,并且由于首次返回给浏览器的就是一个完整的页面,所以对 SEO 更友好。这两点也是后面“激活”的目的所在。他的缺点则体现在后续部分,相对于 CSR 的 SPA 而言,它的后续页面切换需要重新渲染整个页面,这导致他的交互体验不如 SPA。

CSR 的 SPA 优缺点则与传统 SSR 相反,但是它的开发体验好,这是由于 SPA 单页应用的开发必须依赖于一个框架,由该框架来提供单页应用的编译和构建。

SSG 预渲染和岛屿 ​

SSG 的原理 ​

SSG 是建立在 SPA 上的一个取巧的思路,当我们编写一个静态网站的时候可以用到。所谓的静态网站,即对于所有的用户请求,某一路由返回的网站资源是一模一样的。页面本身是非动态页面,即所有用户访问拿到的资源一致,对于这类页面,一般都是博客、公示性内容,所以他们更需要使用 SSR 来提高 SEO。但是原始的 SPA 本身却是基于 CSR 的,并没有很好的 SEO。 这就是 SSG 要解决的问题。

基于非动态页面特点,如果我们在 SPA 构建打包的时候,针对该路由模拟一遍用户请求,输出加载出的 html 等资源(也就是渲染出的页面,Pre-rendering),并建立路由对应的目录,并在 js 中的路由中动一下手脚,要求用户访问该 SPA 的该页面路由则不拦截,而是直接向后端发请求,并拿到对应的资源。

SSG 本质上就是在 SPA 的构建阶段进行了预渲染,并因为这一举动为它从纯的 CSR 变成了 SSR。对于全部路由都返回给用户一样的界面的网站,即为纯的 CSR。

对于部分路由的 CSR,这被称为 ISR (Incremental Static Regeneration) 增量式网站渲染。

带激活的 SSR ​

由于 SPA 本身是一个跑在客户端的应用(CSR),所以它想要享有 SSR 的特性并不容易。SSG 通过在构建阶段让 SPA 转换成了纯 SSR 的应用。而激活(又翻译成水合,hydration)也类似如此。

激活的原理,类似于 SSG,都是试图在构建的时候做一些手脚(所谓“脱水”),尽可能的生成出能够在请求的时候,就返回真实的的 html 和样式字符串,做到类似 SSR 的目的。这种行为让它能够变成一个 SSR 的应用,即客户请求哪个页面,服务器才响应哪个页面。这种 SSR 被称为带激活的 SSR,带激活的 SSR 的专有名词叫做“同构渲染(Rehydration)”。

缺陷 ​

带激活的 SSR 改进了交互中的 首次绘制时间,但 可交互时间 却产生了负面影响。这是因为即使用户看到了页面(HTML已经完全加载),但是负责页面的交互逻辑的大量JS代码(“注水”过程)并未跑完。这导致,它响应的页面看起来具有欺骗性,即页面是一个“死页面”,用户看到的页面上的按钮仍然不可交互,因为它只有一副空壳,内部的逻辑还没有被加载与激活(注水)以能够使用。

部分激活 ​

由于上面提到的缺陷,为了降低激活的首屏成本,激活削减了一些不必要的代码:

  • 渐进式激活,即注水过程并不一次性完成,而是顺序性的分批执行,先注入用户首屏可见的交互逻辑,再注入剩下的部分;
  • 选择性激活,即页面优先激活指定的必要的组件,在激活剩余组件,以减少不必要的 JavaScript 执行。

多页应用与岛屿 ​

为什么使用多页应用 ​

内容展示优先的网站(播客,文章等信息浏览的读者网站)实际上占据了互联网的大部分。这类网站有一个非常显著的特点,那就是,屏幕上的绝大部分面积中的内容都是不可交互性内容,或者仅仅包含简单的交互性逻辑(例如文字划线)。这类网站及其适合 SSR 开发,而不适合使用 CSR:

  • CSR。即使用单页应用来开发。部署纯的 CSR 不利于 SEO,并且首屏体验差。所以 SPA 才有了 SSG 这种转换为 SSR 的技术,来同时获得 SPA 的开发体验以及 SSR 的生产部署。但要求路由必须是纯的非动态页面,这导致它其实比较局限。而 SPA 的 SSR 激活技术弥补了这一缺陷,但是技术还不够普及、首屏的可交互速度仍然不尽人意。
  • SSR。使用多页应用来开发。这与传统的服务器端框架像 PHP、WordPress、Laravel 等使用了几十年的方法相同。一切仍然只是 HTML、CSS 和 JavaScript。

岛屿 ​

Astro 引入的岛屿架构用于多页应用。对于一个路由而言,它本身遵循服务器端渲染的原则,页面以 html 和 css 的形式返回。但是在开发该页面的时候,它允许我们在该页面中书写其他支持 SSR 的框架的代码。该部分的代码,在构建的过程中调用相应框架的处理程序处理成 html 和 css(即脱水)。每一部分的这种代码称为一个岛屿,可以在上面写 .vue,可以写 .jsx。有点像微前端的概念。

流式渲染 ​

Next.js 中实现了一种流式传输 HTML,它可以理解为在现有的 SSR 的基础上(无论是否水合),增加一个 HTML 流式传输。

在传统的 HTTP 传输中,HTML 总是以文件的形式被传输和加载,而不是流。它改进了这一行为,利用了 HTML 即使没有被加载完也能正常的展示的机制。

html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <!-- Omitted -->
    </head>
    <body class="__className_20951f">
        <p>网页静态信息</p><!--$?-->
        <template id="B:0"></template>
        数据正在加载,请稍等...<!--/$-->
        <script src="/_next/static/chunks/webpack-f0069ae2f14f3de1.js" async=""></script>
        <script>(self.__next_f = self.__next_f || []).push([0])</script>
        <script>self.__next_f.push(/* Omitted */)</script>
        <script>self.__next_f.push(/* Omitted */)</script>
        <script>self.__next_f.push(/* Omitted */)</script>
        <script>self.__next_f.push(/* 还没有一个关闭的 script 标签...

在上面浏览器拿到的HTML后,我们可以注意到仍然存在大量尚未闭合的标签。但是由于 HTML 的容错机制,它还是会展示出加载中的文字内容。这尽可能快的提高了首屏速度。对于更多的技术细节,例如这个过程如何配合激活,暂时没有研究。

参考 ​

segmentfault_流式渲染与分块传输编码

astro_群岛

michaeljier_探索现代渲染模式

腾讯云_现代渲染模式