React & Modern Frontend (445 Qs)•Part 10: Modern React 18, React 19 & Next.js Patterns (317 - 353)
What is renderToPipeableStream and how is it different from renderToString?
renderToPipeableStream is the React 18+ Node.js streaming SSR API from react-dom/server. It streams HTML in chunks, so users can see content earlier and React can coordinate Suspense boundaries during server rendering.
renderToString is the older API that generates the entire HTML string first and sends it only after rendering is fully complete.
Key Differences
| Aspect | renderToString | renderToPipeableStream |
|---|---|---|
| Rendering model | All-at-once | Streaming/chunked |
| Suspense on server | Limited fallback behavior | First-class Suspense streaming |
| Time to first byte | Usually slower for large pages | Faster, can flush shell early |
| UX for slow data | Entire page waits | Progressive reveal with fallbacks |
| Best for | Small/simple SSR pages | Modern SSR apps with async data |
renderToString example
javascript
import express from "express";
import { renderToString } from "react-dom/server";
import App from "./App";
const app = express();
app.get("/", (req, res) => {
const html = renderToString(<App />);
res.send(`<!doctype html><html><body><div id="root">${html}</div></body></html>`);
});
renderToPipeableStream example
javascript
import express from "express";
import { renderToPipeableStream } from "react-dom/server";
import App from "./App";
const app = express();
app.get("/", (req, res) => {
const { pipe } = renderToPipeableStream(<App />, {
onShellReady() {
res.setHeader("Content-Type", "text/html");
res.write("<!doctype html><html><body><div id=\"root\">");
pipe(res);
},
onAllReady() {
res.end("</div></body></html>");
},
onError(error) {
console.error(error);
},
});
});
In practice, prefer renderToPipeableStream for React 18+ SSR because it improves perceived performance and works naturally with Suspense-driven progressive loading.