现代 Web 应用需要快速加载——用户不会等待整个页面渲染完毕后再访问内容。客户端渲染等传统方法经常会引入延迟,尤其是在数据获取依赖于大型 JavaScript包的情况下。Next.js流式传输由 React Server Components 和 Suspense提供支持,通过在页面内容准备就绪后逐步将其发送到浏览器来解决此问题。结果:更快的首次内容呈现时间和更流畅的用户体验。
为什么在 Next.js 中使用流?
流式渲染不仅仅是一种性能优化,它还能解决现代前端开发中的特定问题。如今的用户需要即时反馈,但传统的渲染方法往往会造成延迟,导致界面反应迟钝。
通过允许服务器在页面部分内容准备就绪时将其发送到浏览器,流式传输消除了等待页面完整渲染的困扰。用户几乎可以立即查看有意义的内容并进行交互。这种方法不仅提升了感知性能,还消除了用户因观看加载旋转动画或盯着空白占位符而产生的沮丧感。
想象一下这样的场景:您有一个大型网页,其中大部分内容都是静态的或可快速检索的。然而,其中一个组件依赖于一个速度较慢的外部 API。如果没有流式传输,整个页面都会在等待该请求时延迟。使用流式传输后,页面的大部分内容会立即显示,只有速度较慢的组件稍后才会加载。对于用户来说,即使某些元素加载时间较长,这也能带来快速、响应迅速的体验。
Next.js 流式渲染 vs 客户端渲染 (CSR)
乍一看,流式渲染和客户端渲染似乎有一个共同的目标:快速向用户提供内容,而无需等待整个页面加载完毕。然而,它们解决这个问题的方式却截然不同。
客户端渲染(CSR)
使用 CSR,服务器仅发送一个精简的 HTML shell 和一个 JavaScript 包。由于客户端组件无法异步,因此它们无法在渲染过程中有效地获取数据。因此,开发人员必须依赖useEffect进行任何异步操作。
这种方法有三个主要缺点:
更大的捆绑包——必须向浏览器发送更多的逻辑和组件。
延迟数据获取——网络请求只能在JavaScript 下载、解析、执行并到达组件后才能启动useEffect。与服务器端方法相比,这显著延迟了数据检索。
棘手的安全性——由于数据获取发生在客户端,因此可能需要暴露或小心处理 API 密钥等敏感信息。
流媒体
流式渲染则采用相反的方法。由于渲染和数据获取都在服务端进行,应用程序可以立即开始检索数据。页面的任何部分准备就绪后,就会立即发送到浏览器并显示出来——无需等待整个 UI 完成。这种方法缩短了首次有效绘制的时间,并消除了 CSR 中常见的“瀑布”效应。
Next.js Streaming 如何与 React 服务器组件协同工作
在 Next.js 中,流式传输利用React Server Components和Suspense逐步将 HTML 发送到浏览器,而不是等待整个页面呈现。
具体过程如下:
初始渲染:服务器立即发送准备显示元素(布局、标题、占位符等)的 HTML。
悬念边界:需要较慢数据(API 调用、数据库查询)的组件被包装在特殊
组件中,显示诸如微调器或骨架之类的后备,而不是阻塞整个页面。 渐进式水合:一旦数据解析,服务器就会流式传输该组件的完整 HTML,无缝替换浏览器中的回退。
JS 水合(并行):同时,客户端 JavaScript 逐渐水合页面以使其完全交互。
use在 Next.js 中 使用 React Hook 进行流式传输
Next.js 还支持通过use钩子进行流式传输,这使得 React 组件可以直接在客户端组件内解开承诺。
常见的实现模式如下:
服务器组件启动异步操作并将承诺作为道具传递。
客户端组件利用钩子use来使用数据,并暂停自身直到数据准备好。
为什么这很重要:
对于传统的流式传输,被流式传输的组件必须是用于获取数据的服务器组件。客户端组件默认无法异步传输,因此无法直接参与流式传输。
这个use钩子解决了这个限制:它允许客户端组件使用 Promise 并暂停。这意味着您可以流式传输数据并立即添加交互功能(例如事件处理程序和 UI 状态),而无需在服务器和客户端之间拆分逻辑。
这为开发人员提供了更大的灵活性:数据加载仍然在服务器上开始,而最终的渲染和交互元素可以驻留在客户端组件中。
Next.js 流式传输需要 JavaScript 吗?
重要的是要理解,流媒体并不会消除浏览器中对 JavaScript 的需求。虽然服务器负责数据获取和初始 HTML 渲染,但浏览器仍然需要 JavaScript 来将临时占位符(例如旋转器或骨架)替换为实际到达的内容。
如果没有 JavaScript,用户只会看到静态占位符,从而阻止渐进式内容传送,即使服务器已经生成了此内容。
本质上,流媒体加速了内容传送,但 JavaScript 对于将临时元素与最终渲染的组件交换仍然至关重要。
Next.js 流式传输示例及代码(SSR、CSR 与 Streaming)
代码库链接:https ://github.com/u11d-com/blog_nextjs-streaming
为了在实际场景中演示流式渲染的优势,我构建了一个非常简单的 Next.js 演示应用程序,用于获取并显示一个搞笑笑话列表。该项目并列比较了三种渲染策略:传统的服务器端渲染 (SSR)、客户端渲染 (CSR) 以及较新的带有流式渲染的 React 服务器组件(采用服务器和客户端组件方案)。
该应用程序针对每种渲染方法提供单独的页面:
SSR 页面:在服务器上渲染所有内容,并在单个响应中提供完整的 HTML。只有在服务器处理整个页面后,用户才能看到内容。
CSR 页面:提供精简的 HTML 外壳,JavaScript 负责处理客户端的所有数据获取。用户会一直看到加载状态,直到数据获取和渲染完成。
流式页面:利用 React 服务器组件,在 UI 组件准备就绪时逐步将其发送到浏览器。这允许用户在较慢的组件继续加载的同时与可见内容进行交互。
使用 Hook的流式页面use:使用 React useHook 将数据直接流式传输到客户端组件。这不仅支持渐进式渲染,还能实现即时交互,因为客户端组件既可以使用异步数据,又可以附加事件处理程序,而无需等待页面完整加载。
该应用程序包括一个内置分析系统,可跟踪每个页面部分的渲染时间,帮助直观地了解 SSR、CSR 和流式方法之间的性能差异。
您可以自己获取并运行该应用程序,但让我们讨论一下结果及其含义。
Next.js 性能基准:SSR、CSR 和 Streaming
我们的基准测试清楚地显示了每种渲染策略在数据获取时间和内容传递方面的差异。
SSR(服务器端渲染)
开始获取笑话: ~3 毫秒(几乎立即)
笑话列表渲染时间: ~2451 毫秒
页面渲染时间: ~2722 毫秒
分析:虽然数据获取会立即开始,但用户在所有数据处理完毕之前什么也看不到。这会导致首次绘制速度变慢,尽管服务器前期已经做了一些工作。
CSR(客户端渲染)
开始获取笑话: ~281 毫秒
页面渲染时间: ~281 毫秒(初始骨架/旋转器)
笑话列表渲染时间: ~2545 毫秒
分析:使用 CSR 时,数据获取需要等待 JavaScript 加载(约 281 毫秒)。用户很快就能看到占位符,但实际内容却需要等待超过 2.5 秒。
重要提示:页面越大、越复杂,JS 包就越大,执行时间也就越长。这会进一步延迟数据获取,增加获取有意义内容的时间,并损害用户体验。
流媒体
开始获取笑话: ~4 毫秒
页面渲染时间:约 272 毫秒
笑话列表渲染时间: ~2255 毫秒
分析:流媒体提供最佳体验:数据提取立即开始,初始内容在约 272 毫秒内出现,较慢的组件逐渐加载。
相较于 CSR 的主要优势:流式传输不依赖于 JavaScript 包的大小来开始获取数据。这使得即使是复杂的页面也能几乎立即开始加载有意义的内容,从而显著提升感知性能。
结论:为什么流式传输最适合 Next.js 性能
比较 Next.js 中的 SSR、CSR 和 Streaming 可以发现每种方法都有不同的权衡:
SSR会立即在服务器上开始数据提取,但为用户提供较慢的首次内容呈现时间。
CSR将数据获取推迟到 JavaScript 包执行之后,而页面越大,延迟时间越长,导致等待有意义内容的时间越长。
流式传输兼具两者的优势:近乎即时的数据获取、快速的初始内容渲染以及渐进式的组件交付。由于流式传输不依赖于包大小来开始加载数据,因此即使在复杂的页面中也能确保快速的感知性能。
底线:对于优先考虑用户体验和速度的现代 Next.js 应用程序,流式传输提供了响应最快且可扩展的解决方案。
常见问题解答部分
关于 Next.js Streaming 的常见问题 (FAQ)
问:Next.js 中的流式传输是什么?
答:流式渲染是一种渲染技术,服务器会在页面部分内容准备就绪后立即将其发送到浏览器。用户无需等待整个页面渲染完成,几乎可以立即看到有意义的内容,从而提升了感知性能。
问:流式传输与客户端渲染(CSR)有何不同?
答:CSR 会等到 JavaScript 包加载完毕后才获取数据,从而延迟了有意义的内容。而 Streaming 会立即在服务器上开始数据获取,并逐步流式传输组件,使页面速度更快、响应更灵敏。
问:流媒体会取代 SSR(服务器端渲染)吗?
答:不是。流式传输是在 SSR 的基础上添加了渐进式交付。SSR 会在渲染后发送完整的页面,而流式传输会分块发送,让用户更早地看到内容。
问:我还需要使用流媒体的 JavaScript 吗?
答:是的。服务器负责数据获取和初始 HTML 处理,同时需要 JavaScript 将占位符替换为实际的流内容,并填充 UI 以实现交互。
问:流媒体中的钩子有什么用处?
答:use hook 允许 React 客户端组件直接使用 Promise。这意味着你可以将数据流式传输到可交互的客户端组件中,而无需在服务器和客户端之间拆分逻辑。
问:我应该何时在 Next.js 应用中使用流式传输?
答:当您的页面包含快速加载和慢速加载的内容时,请使用流式加载。它可确保用户可以立即与可用内容进行交互,同时较慢的组件仍会在后台继续加载。