Understanding React 18 Streaming SSR and Selective Hydration
React 18 introduces Streaming SSR, allowing the server to send HTML in chunks and perform selective hydration, which improves performance by rendering ready sections early and handling asynchronous components via Suspense, with detailed examples of code implementation, error handling, and JS/CSS integration.
Feature Overview
React 18 adds a new server‑side rendering mode called Streaming SSR, which streams HTML to the browser in chunks and enables selective hydration of already rendered parts.
Basic Principles
Using renderToPipeableStream on a Node.js API, the server can send the initial HTML shell, then later stream the content of async components once their data or code is ready.
let didError = false;
const stream = renderToPipeableStream(
<App />,
{
bootstrapScripts: ["main.js"],
onShellReady() {
res.statusCode = didError ? 500 : 200;
res.setHeader('Content-Type', 'text/html');
stream.pipe(res);
},
onError(err) {
didError = true;
console.error(err);
},
}
);Streaming HTML Example
By setting the HTTP response header Transfer‑Encoding: chunked, the server can write the first segment, pause, then write the second segment, demonstrating progressive rendering.
const http = require("http");
const server = http.createServer(async (req, res) => {
if (req.url === "/") {
res.write("<div>First segment</div>");
await sleep(2000);
res.write("<div>Second segment</div>");
res.end();
}
});
server.listen(8080);Selective Hydration
Before React 18, SSR could not split code; developers either omitted SSR for lazy components or waited for all bundles before hydration. With selective hydration, only the parts that are ready are hydrated, while Suspense boundaries pause until their async children resolve.
import { lazy, Suspense } from "react";
const Content = lazy(() => import("./Content"));
export default function App() {
return (
<html>
<body>
<div>App shell</div>
<Suspense fallback={<Spinner />}>
<Content />
</Suspense>
</body>
</html>
);
}Fallback and Error Handling
If a Suspense child throws during SSR, the server replaces the placeholder comment <!--$?--> with <!--$!--> and sends a script that calls the component’s _reactRetry function on the client, allowing a graceful fallback to client‑side rendering.
Data Structures
The implementation relies on four core structures: Segment (a piece of streamed HTML), SuspenseBoundary (a boundary that tracks pending tasks), Task (a unit of rendering work), and Request (the top‑level object that coordinates the whole process).
Main Flow
1. createRequest builds the initial request, root segment, and root task. 2. startWork begins rendering, creating additional tasks for async Suspense children. 3. performWork processes the task queue, handling promises by re‑queueing tasks. 4. flushCompletedQueues writes completed segments to the response, respecting the order of boundaries. 5. When all tasks are finished, the stream is closed.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ByteDance Web Infra
ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
