streaming-ssr
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStreaming Server-Side Rendering
流式服务端渲染(Streaming Server-Side Rendering)
We can reduce the Time To Interactive while still server rendering our application by streaming the contents of our application. Instead of generating one large HTML string containing the necessary markup for the current navigation, we can send the shell first and stream slower parts later. The moment the client receives the first chunks of HTML, it can start parsing and painting the page.
Modern React streaming uses on Node runtimes or on Web Stream runtimes, then hydrates the response with on the client.
renderToPipeableStream()renderToReadableStream()hydrateRoot()我们可以通过流式传输应用内容,在保留服务端渲染的同时缩短交互时间。无需生成包含当前导航所需标记的完整大HTML字符串,我们可以先发送页面框架,再后续传输加载较慢的部分。客户端一收到第一批HTML分块,就可以开始解析和绘制页面。
现代React流式渲染在Node运行时使用,在Web Stream运行时使用,然后在客户端通过进行响应水合。
renderToPipeableStream()renderToReadableStream()hydrateRoot()When to Use
使用场景
- Use this when you want to improve TTFB and FCP by sending HTML incrementally as it's generated
- This is helpful for large pages where waiting for the full HTML would delay the initial paint
- 当你希望通过在HTML生成时逐步发送来优化TTFB和FCP时使用
- 这对大型页面很有帮助,因为等待完整HTML会延迟初始绘制
Instructions
操作步骤
- Use (React 18+) instead of the deprecated
renderToPipeableStreamrenderToNodeStream - Combine streaming with boundaries to stream partial content while slow parts load
Suspense - Use the callback to begin streaming once the critical shell is ready
onShellReady - Handle streaming errors with the callback
onError
- 使用(React 18+)替代已弃用的
renderToPipeableStreamrenderToNodeStream - 结合流式渲染与边界,在慢加载部分处理时流式传输部分内容
Suspense - 当关键页面框架准备就绪时,使用回调开始流式传输
onShellReady - 通过回调处理流式传输错误
onError
Details
详细说明
The initial HTML gets sent to the response object alongside the chunks of data from the App component:
html
<!DOCTYPE html>
<html>
<head>
<title>Cat Facts</title>
<link rel="stylesheet" href="/style.css" />
<script type="module" defer src="/build/client.js"></script>
</head>
<body>
<h1>Stream Rendered Cat Facts!</h1>
<div id="approot"></div>
</body>
</html>Modern React streaming on Node uses :
renderToPipeableStreamjs
import { renderToPipeableStream } from "react-dom/server";
app.use("*", (request, response) => {
let didError = false;
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ["/build/client.js"],
onShellReady() {
response.statusCode = didError ? 500 : 200;
response.setHeader("Content-Type", "text/html");
pipe(response);
},
onError(error) {
didError = true;
console.error(error);
},
});
});If we were to server render the component using , we would have to wait until the entire tree had rendered before sending the response. With streaming, the server can flush the shell early and continue sending slower content as it becomes ready.
ApprenderToString初始HTML会与App组件的数据分块一起发送到响应对象:
html
<!DOCTYPE html>
<html>
<head>
<title>Cat Facts</title>
<link rel="stylesheet" href="/style.css" />
<script type="module" defer src="/build/client.js"></script>
</head>
<body>
<h1>Stream Rendered Cat Facts!</h1>
<div id="approot"></div>
</body>
</html>Node环境下的现代React流式渲染使用:
renderToPipeableStreamjs
import { renderToPipeableStream } from "react-dom/server";
app.use("*", (request, response) => {
let didError = false;
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ["/build/client.js"],
onShellReady() {
response.statusCode = didError ? 500 : 200;
response.setHeader("Content-Type", "text/html");
pipe(response);
},
onError(error) {
didError = true;
console.error(error);
},
});
});如果我们使用对App组件进行服务端渲染,就必须等待整个组件树渲染完成才能发送响应。而通过流式渲染,服务器可以提前输出页面框架,在较慢的内容准备好后继续发送。
renderToStringConcepts
核心概念
Like progressive hydration, streaming is another rendering mechanism that can be used to improve SSR performance. As the name suggests, streaming implies chunks of HTML are streamed from the node server to the client as they are generated. As the client starts receiving "bytes" of HTML earlier even for large pages, the TTFB is reduced and relatively constant. All major browsers start parsing and rendering streamed content or the partial response earlier. As the rendering is progressive, it results in a fast FP and FCP.
Streaming responds well to network backpressure. If the network is clogged and not able to transfer any more bytes, the renderer gets a signal and stops streaming till the network is cleared up. Thus, the server uses less memory and is more responsive to I/O conditions. This enables your Node.js server to render multiple requests at the same time and prevents heavier requests from blocking lighter requests for a long time. As a result, the site stays responsive even in challenging conditions.
与渐进式水合类似,流式渲染是另一种可优化SSR性能的渲染机制。顾名思义,流式渲染意味着HTML分块在生成后立即从Node服务器流式传输到客户端。即使是大型页面,客户端也能更早收到HTML字节,因此TTFB更短且更稳定。所有主流浏览器都会更早开始解析和渲染流式内容或部分响应。由于渲染是渐进式的,首次绘制(FP)和首次内容绘制(FCP)的时间也会更短。
流式渲染能很好地应对网络背压。如果网络拥堵无法传输更多字节,渲染器会收到信号并停止流式传输,直到网络恢复。因此,服务器占用的内存更少,对I/O条件的响应更灵敏。这使得你的Node.js服务器可以同时处理多个请求,避免重请求长时间阻塞轻请求。因此,即使在复杂条件下,网站也能保持响应性。
React for Streaming
用于流式渲染的React
React 18 introduced the modern streaming APIs:
- for Node.js HTTP responses.
renderToPipeableStream(element, options) - for Web Streams runtimes such as edge environments.
renderToReadableStream(element, options)
These APIs support Suspense boundaries, , , and progressive hydration through on the client.
onShellReadyonAllReadyhydrateRoot()The stream output can emit bytes as soon as the shell is ready. The response progressively sends chunks of data to the client while slower chunks continue rendering on the server.
React 18引入了现代流式API:
- 适用于Node.js HTTP响应。
renderToPipeableStream(element, options) - 适用于边缘环境等Web Streams运行时。
renderToReadableStream(element, options)
这些API支持Suspense边界、、,以及通过客户端的实现渐进式水合。
onShellReadyonAllReadyhydrateRoot()流式输出可以在页面框架准备就绪后立即输出字节。响应会逐步向客户端发送数据分块,同时服务器继续渲染较慢的分块。
Streaming SSR - Pros and Cons
流式SSR的优缺点
Streaming aims to improve the speed of SSR with React and provides the following benefits:
-
Performance Improvement: As the first byte reaches the client soon after rendering starts on the server, the TTFB is better than that for SSR. It is also more consistent irrespective of the page size. Since the client can start parsing HTML as soon as it receives it, the FP and FCP are also lower.
-
Handling of Backpressure: Streaming responds well to network backpressure or congestion and can result in responsive websites even under challenging conditions.
-
Supports SEO: The streamed response can be read by search engine crawlers, thus allowing for SEO on the website.
It is important to note that streaming implementation is not a simple find-replace from to . There are cases where the code that works with SSR may not work as-is with streaming:
renderToStringrenderToPipeableStream()-
Frameworks that use the server-render-pass to generate markup that needs to be added to the document before the SSR-ed chunk. Examples are frameworks that dynamically determine which CSS to add to the page in a precedingtag.
<style> -
Code, whereis used to generate the page template and
renderToStaticMarkupcalls are embedded to generate dynamic content. Since the string corresponding to the component is expected in these cases, it cannot be replaced by a stream. For example:renderToString
js
res.write("<!DOCTYPE html>");
res.write(renderToStaticMarkup(
<html>
<head>
<title>My Page</title>
</head>
<body>
<div id="content">
{ renderToString(<MyPage/>) }
</div>
</body>
</html>);Both Streaming and Progressive Hydration can help to bridge the gap between a pure SSR and a CSR experience.
流式渲染旨在提升React SSR的速度,具有以下优势:
-
性能提升:服务器开始渲染后不久,第一个字节就会到达客户端,因此TTFB比传统SSR更优。无论页面大小如何,TTFB也更稳定。由于客户端可以在收到HTML后立即开始解析,FP和FCP的时间也会更短。
-
处理背压:流式渲染能很好地应对网络背压或拥堵,即使在复杂条件下也能让网站保持响应性。
-
支持SEO:流式响应可以被搜索引擎爬虫读取,因此网站可以支持SEO。
需要注意的是,流式渲染的实现并非简单地将替换为。有些适用于传统SSR的代码无法直接在流式渲染中使用:
renderToStringrenderToPipeableStream()-
那些在服务端渲染阶段生成标记,并需要在SSR分块之前添加到文档的框架。例如,会动态确定页面要添加哪些CSS到前置标签的框架。
<style> -
使用生成页面模板,并嵌入
renderToStaticMarkup调用以生成动态内容的代码。因为这些场景需要组件对应的字符串,无法用流替代。例如:renderToString
js
res.write("<!DOCTYPE html>");
res.write(renderToStaticMarkup(
<html>
<head>
<title>My Page</title>
</head>
<body>
<div id="content">
{ renderToString(<MyPage/>) }
</div>
</body>
</html>);流式渲染和渐进式水合都有助于缩小纯SSR和纯CSR体验之间的差距。