人人都会AI编程

25.4 流式渲染与 Suspense 配合

更新时间:2026-07-10

流式渲染的本质

在传统的服务端渲染(SSR)中,服务器必须等待页面的所有数据准备就绪后,才能生成完整的 HTML 字符串,然后一次性返回给客户端。这会导致“全有或全无”的体验:哪怕页面中只有一个组件在等待慢速接口,整个页面的渲染都会被阻塞。

流式渲染(Streaming SSR)打破了这一限制。它允许服务器将 HTML 分块传输,客户端可以立即处理已收到的内容,而不用等待全部生成完毕。技术层面,这是通过 HTTP 的 Transfer-Encoding: chunked 实现的:服务器不断向响应流中写入数据块,浏览器在接收这些块的同时就开始解析和渲染页面。

Suspense:流式渲染的边界标记

Suspense 在这里扮演着“流式边界”的角色。当一个组件被 <Suspense> 包裹时,React 会:

  1. 先渲染并发送 fallback 中指定的备用内容(如骨架屏、加载指示器);
  2. 当包裹的组件所需数据就绪后,再将最终内容补发到同一个流中;
  3. 客户端接收到最后的内容后,用 React 的选择性水合(Selective Hydration)机制激活该部分的交互。

这种模式将“等待数据”的时间从整体的阻塞变为局部的异步填充,大幅提升了首屏内容的可见速度。

React 18 中的底层 API

React 18 提供了 renderToPipeableStreamrenderToReadableStream 两个 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>
  );
}

在流式传输过程中:

  1. 服务器会立刻发送 <Header /><h1> 以及一个空的占位 <div>(实际上会先发送 fallback 的骨架屏内容)。
  2. LatestArticles 的数据从数据库返回后,服务器将实际的 <ul> 内容编码为 JSON 并推送到流中。
  3. 客户端 React 收到这个更新后,用新内容替换骨架屏,并完成水合。

对流式渲染的最佳实践

  1. 合理设计 Suspense 边界

不要把所有异步组件塞进同一个 Suspense,那会退化成传统的全页面阻塞等待。为每个关键的数据块设置独立的边界,让用户看到页面逐步“点亮”。

  1. 选择合适的 fallback

骨架屏(Skeleton)比简单的 loading 文字更能减少布局偏移,提升视觉流畅度。可以配合 CSS 动画模拟加载状态。

  1. 处理流式渲染的超时

如果某个异步组件长时间未返回,整个流可能无法结束。此时可以考虑在服务端设置超时逻辑,回退到 fallback 或抛出一个可控的错误。

  1. 与水合策略配合

流式渲染默认为所有内容进行水合,但对于纯展示的服务端组件,Next.js 等框架会自动跳过客户端水合,进一步减少 JS 体积和执行时间。

  1. 注意 SEO 的影响

流式传输的 HTML 中会先包含 fallback,搜索引擎爬虫是否能正确抓取到最终内容?现代主流搜索引擎(Google、Bing)已经支持渲染 JavaScript,可以等待异步内容加载完毕,因此影响较小。但仍然建议确保关键 SEO 内容尽早出现在初始 Shell 中。

总结

流式渲染与 Suspense 的结合,是 React 从“一次性交付”到“渐进式交付”的关键跨越。在 RSC 架构下,开发者无需手动编写复杂的流控制逻辑,只需在组件中声明数据依赖,并用 Suspense 标记加载边界。这既保持了代码的简洁,又将页面的感知性能提升到了新的层次,是构建现代高性能 Web 应用不可或缺的技术手段。