近年来,网络开发领域见证了各种面向服务器的渲染模型的发展,部分是因为单页面应用(SPA)的广泛运用。在本文中,我们将讨论流式服务器端渲染(Streaming SSR),旨在消除服务器端渲染(SSR)这种最流行的渲染模式的一些缺点。
为了更好地理解这些渲染模式的影响,我们还经常会参考 Web Vitals 指标,比如 TTFB、TTI、FPC 或 CLS。
- TTFB(Time to First Byte,首字节时间):TTFB 表示从浏览器发出请求到接收到服务器的第一个字节所花费的时间。较低的 TTFB 通常意味着服务器响应速度更快,有助于加快网页加载时间。
- TTI(Time to Interactive,可交互时间):TTI 表示页面从开始加载到变得可交互所需的时间。这是一个重要的指标,因为它反映了用户可以开始与页面进行实际交互的时间点。
- FCP(First Contentful Paint,首次内容绘制):FCP 表示浏览器首次绘制任何内容的时间点。较低的 FCP 通常意味着用户能够更快地看到页面上的内容,这有助于提高用户体验。
- CLS(Cumulative Layout Shift,累积布局位移):CLS 衡量了页面上元素布局变化的不稳定性。这是一个关键指标,因为用户在页面加载过程中如果出现突然的布局变化,会影响其体验。
那么,什么是流式服务器端渲染(Streaming SSR),它在Web开发领域又有何不同之处呢?让我们踏上探索这种激动人心的新渲染模式的旅程。
什么是服务器端渲染(SSR)?
服务器端渲染是一种流行的渲染模式之一,在这种模式下,网页在发送到客户端浏览器之前首先在服务器上进行渲染。与客户端渲染相比,客户端渲染大部分渲染工作由浏览器处理,而服务器端渲染则是在服务器上进行渲染。
如何运行的:
- 客户端向服务器发送请求。
- 在服务器上生成HTML。服务器有能力为每个请求创建新的HTML内容。然后将此HTML发送到浏览器。(TTFB点)
- 浏览器解析并渲染这个HTML。此时,我们有一个可以显示给用户的UI,但用户无法与之交互,因为我们需要JavaScript。
- 浏览器发送请求以获取必要的JavaScript并下载它。
- 浏览器执行 JavaScript 并进行交互作用。(TTI 点)
服务器端渲染的一些关键优势包括:
- 改进的SEO:由于初始页面加载完全在服务器上呈现,搜索引擎可以轻松抓取和索引内容。与纯粹的客户端渲染相比,这可能会导致更好的搜索引擎排名,因为搜索引擎可能难以解析纯客户端渲染的内容。
- 可访问性:SSR 可以增强可访问性,因为初始的 HTML 内容是立即可用的,这样屏幕阅读器和其他辅助技术更容易解释内容。
- 更好的用户体验:由于用户收到了完全呈现的页面,他们可以更快地开始消费内容,从而提供更好的用户体验。同时也没有闪烁的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渲染:
流媒体SSR渲染:
让我们逐步分析,试着理解它是如何运行的
- 客户端向服务器发送请求。
- 服务器现在返回一个使用节点流进行数据传输的机制,同时向浏览器返回一个基本的HTML文件。(这个基本的HTML文件也包含了用于SEO的必要数据,因此我们不会有任何SEO的劣势。)
- 客户端接收到这个HTML并解析它,就像在SSR中的行为一样。节点流数据流也从这一点开始。
- 服务器将完成渲染组件的UI和必要的JS分块传输到浏览器。
- 浏览器执行并使这些块执行就绪。
流媒体 SSR 的 Web Vital 指标
这种方法最显著的好处之一是无需等待整个服务器渲染的内容生成。因此,首字节时间(TTFB)显著减少。
SSR与流式SSR的Web核心指标比较
另一个值得注意的优势与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呢?
- 组件特定数据获取
每个组件都应负责获取自己的数据。主要思想是分离组件的渲染,并确保任何已准备好数据的组件都可以单独显示。因此,我们需要开始为每个组件拆分数据请求。
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)完成,从而改善用户体验。这样,用户可以在实际内容准备好之前看到加载指示器或占位符,增强整体用户体验。