什么是流式 SSR
流式 SSR(Streaming Server-Side Rendering)是一种将服务端渲染和流式传输结合起来的技术。与传统的 SSR 不同,流式 SSR 可以在服务端渲染的同时,逐步将渲染结果传输到客户端,实现页面的渐进式展示。
在流式 SSR 中,服务端会根据客户端的请求,逐步生成页面内容,并将它们作为流式数据流式传输到客户端。客户端可以在接收到一部分数据后,就开始逐步显示页面,而不需要等待整个页面渲染完成。这种方式可以有效提高页面的加载速度和用户体验。
(流式 SSR 的页面加载过程)
使用流式 SSR 的收益
-
减少设备和网络情况的副作用
这是 SSR 渲染模式相比于传统 CSR 渲染模式带来的优势,对于流式 SSR 应用同样适用。 CSR 渲染模式需要在终端设备上完成资源加载、数据加载以及整个渲染过程,受端侧设备自身 CPU 及网络性能影响大,如设备 CPU 性能不足或网络波动较大,则此时整个页面加载性能将严重下降,这也是为什么很多页面在高端设备上加载速度较快,但在低端设备上需要7-8秒的原因。
而 SSR 的渲染模式,则在服务端提供了高性能的渲染容器及网络环境,服务端的渲染,不受端侧设备 CPU 或网络影响,始终提供稳定的渲染性能表现。

-
减少接口过慢对首屏性能的影响
通常,页面会由多个区块组成,其中一些区块不依赖于数据,而其他区块可能依赖于快速或缓慢响应的接口。
现有 SSR 渲染模式存在的一个不足是,整个渲染过程是同步的,需要在页面渲染之前完成所有数据请求,并一次性返回整个页面的 HTML。如果页面的某些接口响应过慢,将会导致整个页面的响应时间过长。
流式渲染的最大好处在于它可以分块返回页面内容。例如,当请求进入时,它可以首先完成页面静态内容的渲染并响应给端侧进行渲染,等待其他依赖于数据的区块完成渲染后再分块返回。这样整个页面的渲染过程不再绑定在一起,而是一个异步的过程,先完成渲染的部分将先返回,从而优化了页面响应速度。
-
提前资源的加载时机
流式 SSR 相比传统 SSR 应用有另一个额外的收益是,由于 HTML 可以分块返回,页面的资源信息可以随第一个 HTML 片段一起下发,从而尽快开始加载。相比之下,在传统 SSR 应用中,资源信息必须等待整个 HTML 完成渲染后下发,请求开始的时机会受到渲染过程的阻塞。
采用流式 SSR,可以使资源请求和页面渲染过程并行进行,进一步提升了页面的性能表现。

-
提升页面的可交互时间
在 SSR 渲染模式下,将页面节点达到可交互状态的过程称为 Hydrate,它需要在端侧执行 JavaScript。
由于页面资源可以提前下发,并且 React 18 对 Hydrate 进行了异步化处理,在流式 SSR 应用中,可以进一步实现先渲染的页面先达到可交互状态的效果。对于部分首屏接口较慢的应用,这将进一步提升页面的可交互体验。

基本工作原理
流式 SSR 的实现,最基本的原理是:
- 基于 HTTP 协议中的 chunked 编码规范,设置响应头的 Transfer-Encoding 为 chunked 对 HTML 内容进行分块传输。
- 在浏览器侧,流式地读取数据并进行渲染,这是主流浏览器默认支持的。
结合 Node.js 内置的 HTTP 模块,实现一个最简单的流式 DEMO 示例如下:
js
const http = require('http');
const server = http.createServer(async (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Transfer-Encoding', 'chunked')
// 分区块的传输页面内容
res.write('<html>');
res.write('<head><title>Stream Demo</title><head>');
res.write('<body>');
// 模拟服务端暂停
await sleep(3000);
res.write('<h2>Hello</h2>');
await sleep(3000);
res.write('<h2>ICE 3</h2>');
res.write('</body></html>');
res.end();
});
server.listen(3000);
基于这个基本原理,将页面分为骨架屏和几个区块,并行地渲染这些区块,然后将渲染好的区块分段返回,就可以实现基本的流式 SSR 。
WebView 接收流式Chunk渲染的实现原理(iOS)
核心组件
iOS WebView 接收流式 Chunk 渲染基于 NSURLProtocol 拦截机制 + NSURLProtocolClient 回调机制 实现。
NSURLProtocol(请求拦截器):
js
// 拦截 WebView 的网络请求
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return [self shouldInterceptRequest:request];
}
- (void)startLoading {
// 转发给自定义处理器
[[SSRHandler shareInstance] sendRequest:self.request delegate:self];
}
NSURLProtocolClient(数据传递桥梁):
js
// 系统提供的协议,用于向 WebView 传递数据
@protocol NSURLProtocolClient
- (void)URLProtocol:didReceiveResponse:cacheStoragePolicy:; // 响应头
- (void)URLProtocol:didLoadData:; // 数据块(可多次)
- (void)URLProtocolDidFinishLoading:; // 完成
- (void)URLProtocol:didFailWithError:; // 错误
@end
渲染流程
-
阶段一:请求拦截
objectivecWebView 发起请求 → NSURLProtocol 拦截 → 转发给 SSR 处理器 -
阶段二:响应处理
csharp// 1. 返回响应头 [client URLProtocol:protocol didReceiveResponse:response cacheStoragePolicy:policy]; // 2. 流式返回数据(关键步骤) [client URLProtocol:protocol didLoadData:chunk1]; // 第1块 [client URLProtocol:protocol didLoadData:chunk2]; // 第2块 [client URLProtocol:protocol didLoadData:chunkN]; // 第N块 // 3. 标记完成 [client URLProtocolDidFinishLoading:protocol]; -
阶段三:WebView 渲染
css每次 didLoadData 调用 → WebView 增量解析 HTML → 实时渲染到页面
数据流图示如下:
scss
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ WebView │───▶│NSURLProtocol │───▶│ SSR Handler │
│ │ │ │ │ │
│ │◀───│ │◀───│ │
└─────────────┘ └──────────────┘ └─────────────┘
▲ │
│ ▼
增量渲染 NSURLProtocolClient
▲ │
│ ▼
┌─────────────────────────────┐
│ didLoadData (chunk1) │
│ didLoadData (chunk2) │
│ didLoadData (chunkN) │
│ URLProtocolDidFinishLoading │
└─────────────────────────────┘