示例说明
传统 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);
},
});