res.write()未立刻发送是因为http响应默认缓冲,需设置content-type、cache-control、connection响应头并禁用缓冲,配合res.flush()或流式读取才能实现实时传输。

res.write() 为什么没立刻发到浏览器?
因为 HTTP 响应默认是「缓冲发送」的,res.write() 只是把数据塞进内部缓冲区,并不强制刷出。浏览器收不到任何内容,直到 res.end() 被调用(或缓冲区满、连接关闭)。
要让数据实时到达前端,必须显式设置响应头并禁用缓冲:
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' })- 不能用
res.send()或res.json()—— 它们会自动调用res.end() - 避免在
res上监听'close'后再写,此时连接可能已断
HTML 流式传输必须设哪些响应头?
少了关键响应头,浏览器会等完整响应才开始解析,彻底失去流式意义。最简必要组合是:
-
Content-Type: text/html; charset=utf-8—— 明确告知是 HTML,且编码正确 -
Cache-Control: no-cache—— 防止中间代理或浏览器缓存整块响应 -
Connection: keep-alive—— 维持长连接,否则每次write都可能触发连接重置 - 不要设
Content-Length—— 流式传输长度未知,设了反而导致浏览器卡住等待
如果用了 Nginx 反向代理,还需额外配置:proxy_buffering off; 和 chunked_transfer_encoding on;,否则 Nginx 会攒够一整块才转发。
前端如何接收并渲染流式 HTML 片段?
不能用 fetch() + .text(),那会等到流结束;也不能用 XMLHttpRequest 的 responseType = 'document'(多数浏览器不支持流式 DOM 解析)。
可靠做法只有两种:
- 用
fetch()+response.body.getReader()逐块读取,手动DOMParser().parseFromString(chunk, 'text/html')提取节点并appendChild—— 注意处理碎片化标签(比如<div> 被切在两块里) <li>更简单:后端输出带 <code><script></script>的 HTML 片段,前端用<script src="/stream"></script>动态加载 —— 浏览器会自动执行每段脚本,适合更新局部 UI - 没设
Content-Type,浏览器可能当下载文件处理(尤其application/octet-stream) - 没处理
error事件 —— 文件不存在时pipe()不抛错,res却卡住,客户端永远白屏 - 没设
res.on('error', ...)—— 网络中断时流会崩,但服务端无感知
别依赖 document.write():它只在页面加载中有效,流式场景下多数已被忽略或报错。
pipe() 传 HTML 文件时为什么页面空白?
fs.createReadStream('index.html').pipe(res) 看似简洁,但容易出问题:
安全写法:
const stream = fs.createReadStream('index.html');
res.setHeader('Content-Type', 'text/html; charset=utf-8');
stream.pipe(res);
stream.on('error', (err) => {
console.error(err);
res.statusCode = 500;
res.end('HTML file not found');
});
res.on('error', () => stream.destroy());
流式 HTML 的核心不是“快”,而是“可控”——每一块输出都得可中断、可降级、可追溯。最容易被忽略的是错误传播路径:后端流崩了,前端可能还在傻等,连 loading 都没关掉。











