流式渲染依赖 HTTP/1.1 的 Transfer-Encoding: chunked 实现分块推送与实时解析,需服务端发送语法完整的 HTML 片段、前端正确处理流,且各环节解耦。

流式渲染不是靠前端“模拟”出来的,而是依赖 HTTP/1.1 的 Transfer-Encoding: chunked 在底层真实分块推送 HTML。只要服务端正确启用分块编码、浏览器支持 Streams API,就能实现「收到一块、解析一块、渲染一块」——无需等整个 HTML 构建完成。
为什么必须用 Transfer-Encoding: chunked 而非 Content-Length
浏览器看到 Content-Length 就会等待收满指定字节数才开始解析;而 Transfer-Encoding: chunked 明确告诉浏览器:“我不知道总长,但每块都有自己的长度前缀,来一块就处理一块”。Node.js 中只要不设 Content-Length 且多次调用 res.write(),就会自动加这个 header。
- 显式设置
res.setHeader('Transfer-Encoding', 'chunked')是冗余的,甚至可能出错(比如和框架冲突) - 如果用了 Koa 或 Express,默认 body 是字符串或 Promise,会自动计算并设置
Content-Length,导致无法流式 —— 必须把ctx.body设为Readable流实例 - Chrome/Firefox/Safari 均原生支持 chunked HTML 渲染,但 Safari 对非闭合标签更敏感(比如提前写
<div>没配对,可能卡住)
res.write() 发送 HTML 块时的关键约束
每个 res.write() 调用发送的必须是语法完整的 HTML 片段,否则浏览器解析器会暂停等待补全。不是“任意字符串”,而是“可独立插入 DOM 的最小合法单元”。
- 推荐以完整标签为单位:比如
res.write('<p>第一段文字</p>'),而不是res.write('<p>')单独发开标签 - 可以发多个嵌套标签,只要闭合:如
res.write('<section><h2>标题</h2><p>内容</p></section>') - 避免在块中间切断属性值或引号:
res.write('<div class="') → 错误;应合并为 <code><div class="card"> - 中文需确保响应头带
charset=utf-8,否则res.write()可能乱码(Node.js 默认按 UTF-8 编码 Buffer,但字符串若含 BOM 或编码不一致会出问题)
前端用 fetch() + ReadableStream 拿到 chunk 后不能直接 innerHTML
浏览器已自动解析并渲染 chunked HTML,你用 JavaScript 读取流只是“旁观”,不是“接管”。想干预渲染节奏(比如加打字机动画),就得关掉自动解析,走自定义路径。
立即学习“前端免费学习笔记(深入)”;
- 服务端仍发 chunked HTML,但前端用
response.body获取ReadableStream,再用getReader()手动读取 - 每次
reader.read()返回的是Uint8Array,需用TextDecoder转成字符串,再塞进innerHTML或insertAdjacentHTML - 注意:直接
el.innerHTML += chunkStr可能破坏已有 DOM 结构(比如重绘整个父容器),应优先用el.insertAdjacentHTML('beforeend', chunkStr) - 流未结束前不要调
res.end(),否则reader.read()会立刻返回{done: true},后续 chunk 丢弃
React Server Components 场景下,Suspense 边界就是 chunk 切分点
Next.js App Router 或 RSC 环境中,你不用手动调 res.write()。框架在遇到 <Suspense fallback=> 时,会自动把其内部组件的 HTML 序列化为一个 chunk 推送。
- 一个
Suspense包裹的异步组件(比如await fetch()数据的Page)→ 对应一个 HTML chunk - 多个并行
Suspense可触发多路并发流,但最终 chunk 顺序取决于各 Promise 完成时间,无法保证严格先后 - 如果某个
Suspense内部抛错,会导致该 chunk 渲染失败,但不影响其他 chunk(这是流式容错优势) - 开发时打开 Network 面板,看 response preview 是否“逐步出现内容”,是验证流式是否生效最直接的方式
真正容易被忽略的是:流式不是开关一开就万事大吉。它要求服务端生成逻辑可中断、HTML 片段自包含、前端接收逻辑不阻塞主线程。任何一个环节粘连过紧(比如后端模板引擎必须吐完整 DOM 树才返回),整条链路就退化为普通请求。


















