流式渲染的本质
在传统的服务端渲染(SSR)中,服务器必须等待页面的所有数据准备就绪后,才能生成完整的 HTML 字符串,然后一次性返回给客户端。这会导致“全有或全无”的体验:哪怕页面中只有一个组件在等待慢速接口,整个页面的渲染都会被阻塞。
流式渲染(Streaming SSR)打破了这一限制。它允许服务器将 HTML 分块传输,客户端可以立即处理已收到的内容,而不用等待全部生成完毕。技术层面,这是通过 HTTP 的 Transfer-Encoding: chunked 实现的:服务器不断向响应流中写入数据块,浏览器在接收这些块的同时就开始解析和渲染页面。
Suspense:流式渲染的边界标记
Suspense 在这里扮演着“流式边界”的角色。当一个组件被 <Suspense> 包裹时,React 会:
- 先渲染并发送
fallback中指定的备用内容(如骨架屏、加载指示器); - 当包裹的组件所需数据就绪后,再将最终内容补发到同一个流中;
- 客户端接收到最后的内容后,用 React 的选择性水合(Selective Hydration)机制激活该部分的交互。
这种模式将“等待数据”的时间从整体的阻塞变为局部的异步填充,大幅提升了首屏内容的可见速度。
React 18 中的底层 API
React 18 提供了 renderToPipeableStream 和 renderToReadableStream 两个 API 来支持流式 SSR。开发者可以在 Node.js 或 Edge 环境中直接使用,但更常见的做法是通过框架(如 Next.js)间接使用。
// 底层用法示意,通常由框架封装
import { renderToPipeableStream } from 'react-dom/server';
app.get('/', (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
res.setHeader('Content-Type', 'text/html');
pipe(res); // 开始流式传输
}
});
});
在 RSC 中的实际使用
配合 React Server Components,流式渲染变得异常简单——服务端组件可以直接是异步的,不需要在客户端管理数据加载。
以 Next.js App Router 为例,假设一个页面包含:
- 立即显示的导航栏和标题;
- 一个需要查询数据库的“最新文章列表”,耗时较长。
我们可以在页面中直接用 async 声明需要数据的服务端组件,并用 Suspense 包裹它:
// app/page.js
import { Suspense } from 'react';
export default function Page() {
return (
<div>
{/* 静态内容立即发送 */}
<Header />
<h1>欢迎回来</h1>
{/* 需要等待数据的部分,使用 Suspense 分割 */}
<Suspense fallback={<ArticleListSkeleton />}>
<LatestArticles />
</Suspense>
</div>
);
}
// 这是一个服务端组件,可以直接访问数据库
async function LatestArticles() {
const articles = await db.query('SELECT * FROM articles ORDER BY created_at DESC LIMIT 10');
return (
<ul>
{articles.map((a) => (
<li key={a.id}>{a.title}</li>
))}
</ul>
);
}
在流式传输过程中:
- 服务器会立刻发送
<Header />和<h1>以及一个空的占位<div>(实际上会先发送fallback的骨架屏内容)。 - 当
LatestArticles的数据从数据库返回后,服务器将实际的<ul>内容编码为 JSON 并推送到流中。 - 客户端 React 收到这个更新后,用新内容替换骨架屏,并完成水合。
对流式渲染的最佳实践
- 合理设计 Suspense 边界
不要把所有异步组件塞进同一个 Suspense,那会退化成传统的全页面阻塞等待。为每个关键的数据块设置独立的边界,让用户看到页面逐步“点亮”。
- 选择合适的 fallback
骨架屏(Skeleton)比简单的 loading 文字更能减少布局偏移,提升视觉流畅度。可以配合 CSS 动画模拟加载状态。
- 处理流式渲染的超时
如果某个异步组件长时间未返回,整个流可能无法结束。此时可以考虑在服务端设置超时逻辑,回退到 fallback 或抛出一个可控的错误。
- 与水合策略配合
流式渲染默认为所有内容进行水合,但对于纯展示的服务端组件,Next.js 等框架会自动跳过客户端水合,进一步减少 JS 体积和执行时间。
- 注意 SEO 的影响
流式传输的 HTML 中会先包含 fallback,搜索引擎爬虫是否能正确抓取到最终内容?现代主流搜索引擎(Google、Bing)已经支持渲染 JavaScript,可以等待异步内容加载完毕,因此影响较小。但仍然建议确保关键 SEO 内容尽早出现在初始 Shell 中。
总结
流式渲染与 Suspense 的结合,是 React 从“一次性交付”到“渐进式交付”的关键跨越。在 RSC 架构下,开发者无需手动编写复杂的流控制逻辑,只需在组件中声明数据依赖,并用 Suspense 标记加载边界。这既保持了代码的简洁,又将页面的感知性能提升到了新的层次,是构建现代高性能 Web 应用不可或缺的技术手段。