服务器端渲染进化:通过流式服务器端渲染解锁更快的TTFB和TTI

811 阅读8分钟

近年来,网络开发领域见证了各种面向服务器的渲染模型的发展,部分是因为单页面应用(SPA)的广泛运用。在本文中,我们将讨论流式服务器端渲染(Streaming SSR),旨在消除服务器端渲染(SSR)这种最流行的渲染模式的一些缺点。

为了更好地理解这些渲染模式的影响,我们还经常会参考 Web Vitals 指标,比如 TTFB、TTI、FPC 或 CLS。

  1. TTFB(Time to First Byte,首字节时间):TTFB 表示从浏览器发出请求到接收到服务器的第一个字节所花费的时间。较低的 TTFB 通常意味着服务器响应速度更快,有助于加快网页加载时间。
  2. TTI(Time to Interactive,可交互时间):TTI 表示页面从开始加载到变得可交互所需的时间。这是一个重要的指标,因为它反映了用户可以开始与页面进行实际交互的时间点。
  3. FCP(First Contentful Paint,首次内容绘制):FCP 表示浏览器首次绘制任何内容的时间点。较低的 FCP 通常意味着用户能够更快地看到页面上的内容,这有助于提高用户体验。
  4. CLS(Cumulative Layout Shift,累积布局位移):CLS 衡量了页面上元素布局变化的不稳定性。这是一个关键指标,因为用户在页面加载过程中如果出现突然的布局变化,会影响其体验。

那么,什么是流式服务器端渲染(Streaming SSR),它在Web开发领域又有何不同之处呢?让我们踏上探索这种激动人心的新渲染模式的旅程。

什么是服务器端渲染(SSR)?

服务器端渲染是一种流行的渲染模式之一,在这种模式下,网页在发送到客户端浏览器之前首先在服务器上进行渲染。与客户端渲染相比,客户端渲染大部分渲染工作由浏览器处理,而服务器端渲染则是在服务器上进行渲染。

0_YeCRC1fFv5nOGrZ7.bin

如何运行的:

  1. 客户端向服务器发送请求。
  2. 在服务器上生成HTML。服务器有能力为每个请求创建新的HTML内容。然后将此HTML发送到浏览器。(TTFB点)
  3. 浏览器解析并渲染这个HTML。此时,我们有一个可以显示给用户的UI,但用户无法与之交互,因为我们需要JavaScript。
  4. 浏览器发送请求以获取必要的JavaScript并下载它。
  5. 浏览器执行 JavaScript 并进行交互作用。(TTI 点)

服务器端渲染的一些关键优势包括:

  1. 改进的SEO:由于初始页面加载完全在服务器上呈现,搜索引擎可以轻松抓取和索引内容。与纯粹的客户端渲染相比,这可能会导致更好的搜索引擎排名,因为搜索引擎可能难以解析纯客户端渲染的内容。
  2. 可访问性:SSR 可以增强可访问性,因为初始的 HTML 内容是立即可用的,这样屏幕阅读器和其他辅助技术更容易解释内容。
  3. 更好的用户体验:由于用户收到了完全呈现的页面,他们可以更快地开始消费内容,从而提供更好的用户体验。同时也没有闪烁的UI问题或CLS问题。

SSR的一切都很完美?

很遗憾,不是的。像任何渲染模式一样,SSR 也有其优势和劣势。我们将探讨服务器端渲染(SSR)的两个潜在缺点,即与首字节时间(TTFB)指标相关的初始等待时间和与交互时间(TTI)指标相关的交互延迟。

TTFB — 初始等待时间:SSR 的一个缺点是延长的首字节时间(TTFB),即第一个字节到达用户浏览器所需的时间。这种延迟是因为 SSR 需要在服务器上渲染所有 HTML,包括捆绑 JavaScript,这需要等待必要的数据请求和执行渲染逻辑。因此,用户在初始加载过程中可能会遇到空白屏幕,这可能会影响用户体验。

TTI — 交互延迟:在SSR的情境下,交互时间(TTI)可能会延迟。虽然初始的HTML可能可见,但完整的交互性会因浏览器加载和执行JavaScript而推迟。这种延迟会影响整体用户体验。

React 18 带来了一种新的渲染模式,以减轻这两种不良影响:流式服务器端渲染。

流媒体SSR是什么?

流式服务器端渲染(SSR)是一种在执行服务器端渲染时改善TTFB和TTI的方法。流式的逻辑是我们有一个叫做Node流的概念,它允许我们不断地以片段形式传输数据。

在标准的SSR中,服务器端渲染页面的所有HTML,然后通过捆绑将所有必要的JS传输到客户端,正如前一节所讨论的那样,揭示了TTFB和TTI的缺点。流式SSR通过分别渲染每个组件并逐个创建JS捆绑包,为此提供了解决方案,使我们能够以逐块的方式(我们称之为块)流式传输所有内容,然后在页面上显示。

SSR渲染:

0_jiYO-UiGVK2_0QTT.bin

流媒体SSR渲染:

0_XKnwBY1S7NqSW5Bg.bin

让我们逐步分析,试着理解它是如何运行的

  1. 客户端向服务器发送请求。
  2. 服务器现在返回一个使用节点流进行数据传输的机制,同时向浏览器返回一个基本的HTML文件。(这个基本的HTML文件也包含了用于SEO的必要数据,因此我们不会有任何SEO的劣势。)
  3. 客户端接收到这个HTML并解析它,就像在SSR中的行为一样。节点流数据流也从这一点开始。
  4. 服务器将完成渲染组件的UI和必要的JS分块传输到浏览器。
  5. 浏览器执行并使这些块执行就绪。

流媒体 SSR 的 Web Vital 指标

这种方法最显著的好处之一是无需等待整个服务器渲染的内容生成。因此,首字节时间(TTFB)显著减少。

SSR与流式SSR的Web核心指标比较

0_J-4iAND-wXxj_8OY.bin

另一个值得注意的优势与hydration过程有关。在传统的服务器端渲染(SSR)中,需要执行一个庞大的JavaScript包。这个包包含了页面上所有元素的代码。然而,通过新的方法,我们可以选择性地只执行特定部分的必要代码,从而大大改善了交互时间(TTI)。

因此,一旦最初的块的合并完成,交互时间(TTI)几乎为瞬间。这意味着浏览器在这一点变得可以交互。

使用NextJs 13实现SSR流式传输

我们已经完成了理论部分。现在让我们学习如何使用Next.js来实现这种新的模式。

在 NextJS 13 中,数据获取方法与之前的版本有些变化。现在,使用默认的服务器组件,您可以在组件内直接获取数据,而无需使用任何效果钩子或服务器端属性,如 getServerSideProps 。

例如,您可以在下面看到标准的SSR页面结构:


export default async function Page() {

  /**
   * To shorten the TTFB time, requests are optimized by parallelizing them.
   */
  const productsPromise = getProducts();
  const personsPromise = getPersons();
  const plansPromise = getPlans();

  const [productData, personsData, plansData] = await Promise.allSettled([
    productsPromise,
    personsPromise,
    plansPromise
  ]);

  return (
    <div>
      <Products data={productData} />
      <Persons data={personsData} />
      <Plans data={plansData} />
    </div>
  );
}

在这个结构中,用户在查看页面之前,必须先完成所有请求,然后使用这些数据在服务器端创建页面上的所有组件。由此过程产生的 HTML 和 JS 被发送到浏览器。

那么我们如何在这个结构中实现流式SSR呢?

  1. 组件特定数据获取

每个组件都应负责获取自己的数据。主要思想是分离组件的渲染,并确保任何已准备好数据的组件都可以单独显示。因此,我们需要开始为每个组件拆分数据请求。

 React Suspense React 悬念

我们需要用 Suspense 来包裹 Component ,以便告诉React这个组件可以使用流式SSR进行渲染。

我们已经知道了 Suspense 并且已经用于代码拆分。在React 18中, Suspense 允许我们创建 streamable 组件。更多细节请查看React文档。

让我们仔细看一下以下的实施:

    export default function Page() {
      return (
        <div>
          <Suspense fallback={<Skeleton/>}>
            <Persons />
          </Suspense>
          <Suspense fallback={<Skeleton/>}>
            <Plans />
          </Suspense>
          <Suspense fallback={<Skeleton/>}>
            <Products />
          </Suspense>
        </div>
      );
    }

在流式 SSR 中,每个被 Suspense 包裹的组件在完成自己的数据获取后会完成自己的渲染,然后流式传输到浏览器。这意味着我们不需要等待所有三个请求完成和整个页面渲染结束,就像在标准的 SSR 示例中那样。而且在第一次融合(即 TTI 点)之后,浏览器就变得可以与用户交互了。这样用户就不必等待其他仍在渲染的组件。

流媒体的另一个优点是,你甚至可以在页面上为子组件外部使用它。对此没有限制。因此,您甚至可以将此方法应用于嵌套组件的底层子组件。

除此之外,Next.js 13 还提供了一个依赖于流式传输的功能。这有助于通过在页面的内容部分显示骨架或您想要显示的内容,直到首字节时间(TTFB)完成,从而改善用户体验。这样,用户可以在实际内容准备好之前看到加载指示器或占位符,增强整体用户体验。