Streaming Server‑Side Rendering in React: Concepts, lazy, Suspense, and Implementation
This article explains the principles of streaming server‑side rendering (SSR) in React, compares it with traditional client‑side rendering, and demonstrates how lazy loading and Suspense can be used together with streaming SSR to parallelize data and JavaScript delivery for faster first‑paint and improved user experience.
With the rapid evolution of web technologies, front‑end code has become increasingly complex, leading to larger client payloads and longer first‑paint times; server‑side rendering (SSR) is introduced as a technique to reduce initial rendering latency and improve user experience.
Traditional client‑side rendering (CSR) follows a multi‑step chain: request HTML, request JavaScript, request data, then execute JavaScript, which can delay the first paint. An illustration of this flow is shown below.
SSR reduces the first‑paint (FP) time by sending rendered HTML from the server, but it can introduce a period of non‑interactive time between FP and Time To Interactive (TTI), sometimes leaving users confused by visible content that cannot be interacted with.
Both CSR and SSR start by returning HTML, but CSR then sends JavaScript followed by data, while SSR can send data first and JavaScript later, allowing the page to become interactive sooner after the initial paint.
To further improve performance, streaming SSR is proposed. The ideal streaming SSR flow (illustrated below) uses only two requests: one for HTML that streams a skeleton screen and later the actual data, and another for JavaScript that becomes interactive once executed.
Advantages include parallel data and JavaScript fetching and a reduced Time To First Byte (TTFB) when the two requests are combined.
To enable streaming SSR, existing SSR frameworks need to be upgraded with lazy and Suspense. A minimal lazy implementation is shown below:
function lazy(loader) {
let p;
let Comp;
let err;
return function Lazy(props) {
if (!p) {
p = loader();
p.then(exports => (Comp = exports.default || exports), e => (err = e));
}
if (err) throw err;
if (!Comp) throw p;
return <Comp {...props} />;
};
}The use of throw allows the promise to bubble up to the nearest catch, enabling the framework to pause rendering until the component is loaded. lazy is typically paired with Suspense. A simple Suspense implementation is:
function Suspense({ children, fallback }) {
const forceUpdate = useForceUpdate();
const addedRef = useRef(false);
try {
return children;
} catch (e) {
if (e instanceof Promise) {
if (!addedRef.current) {
e.then(forceUpdate);
addedRef.current = true;
}
return fallback;
} else {
throw e;
}
}
}The logic attempts to render children; if a Promise is thrown, the fallback UI is shown until the promise resolves, after which the component re‑renders.
Other frameworks such as Vue or Preact use similar placeholder mechanisms, but the core idea remains the same: a marker (often a Symbol) indicates a suspended component.
The final piece integrates streaming SSR with an Express server using ReactServerDOM.renderToNodeStream:
app.get("/", (req, res) => {
res.write("<!DOCTYPE html><html><head><title>My Page</title></head><body>");
res.write("<div id='root'>");
const stream = ReactServerDom.renderToNodeStream(<App />);
stream.pipe(res, { end: false });
stream.on('end', () => {
res.write("</div></body></html>");
res.end();
});
});During streaming, the server first sends a skeleton HTML fragment, then later replaces it with the actual content once the associated Promise resolves, using an inline script to swap DOM nodes.
Overall, streaming SSR can significantly reduce render time and improve perceived performance, though it is still largely experimental and under active development in major frameworks.
References:
https://github.com/facebook/react/pull/20970
https://github.com/facebook/react/pull/14717
https://hackernoon.com/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67
https://reactjs.org/docs/react-dom-server.html#rendertonodestream
https://zhuanlan.zhihu.com/p/56587500
https://zhuanlan.zhihu.com/p/358624196
https://github.com/overlookmotel/react-async-ssr
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.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
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.
