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 文件即为单页应用的核心,它负责了所有页面的渲染,包括请求剩下的 cssjs,并创建 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_探索现代渲染模式

腾讯云_现代渲染模式