← 中文资源

简体中文版本

React SSR 流式渲染与 Suspense

用 renderToPipeableStream 做服务端流式渲染:外壳就绪立即冲刷,慢数据被 Suspense 边界包住异步补流,不阻塞首字节。

阅读英文原文

示例说明

传统 SSR 要等全部数据取齐才能输出第一个字节,慢接口会拖慢整个页面。React 18 的流式渲染把组件树分层:静态外壳先渲染先发送,Suspense 边界内的慢组件挂起后以占位内容先出,数据就绪时再把真实内容以内联脚本形式追发到浏览器补位。TTFB 显著提前。

技术要点

  • onShellReady 表示外壳可安全开始输出
  • Suspense fallback 作为流式占位先发送
  • onError 捕获渲染期异常避免进程崩溃

代码解读

Comments 模拟挂起 200 毫秒的数据组件;渲染器在外壳就绪回调里把输出管道接到自定义 Writable,演示分块冲刷过程——标题立即到达而评论区随后补齐。onError 兜住组件树里的异常,保证单个组件失败不放大为进程错误。

使用建议

生产环境应配合客户端 hydration 保持同构。SSE 或大响应要走 node 原生 res 而非内存 Writable。注意 HTTP 头必须在外壳冲刷前写出,之后不可再改。

代码示例

// serverSide streaming render with Suspense boundaries
import { Suspense } from 'react';
import { renderToPipeableStream } from 'react-dom/server';
import { Writable } from 'node:stream';

function Comments({ postId }) {
  throw new Promise((resolve) => setTimeout(resolve, 200))
    .then(() => null)
    .finally(() => resolve());
  return null;
}

function Page({ postId }) {
  return (
    <html>
      <body>
        <h1>Article {postId}</h1>
        <Suspense fallback={<p>loading comments…</p>}>
          <Comments postId={postId} />
        </Suspense>
      </body>
    </html>
  );
}

const out = new Writable({
  write(chunk, encoding, callback) {
    console.log('chunk flushed:', chunk.toString().length, 'bytes');
    callback();
  },
});

const { pipe } = renderToPipeableStream(<Page postId={7} />, {
  onShellReady() {
    pipe(out);
  },
  onError(err) {
    console.error('ssr error:', err.message);
  },
});