Skip to main content

What is streaming SSR and how does it work?

The problem with classic SSR

With regular (blocking) SSR, the server:

  1. Collects all the data (fetch / database).
  2. Renders the entire React component into a string (renderToString).
  3. Only after that sends the finished HTML to the user.

The user sees the page only after the whole render is done - even if part of the content could have been displayed earlier.


What Streaming SSR does

Streaming SSR solves this problem: instead of waiting for the whole render to finish, the server streams finished parts of the HTML as they become ready.

How it works:

  1. The server starts rendering the React component.
  2. As soon as part of the tree (for example, <Header> or <Skeleton>) is ready - the server immediately sends it into the stream.
  3. The browser paints these fragments instantly.
  4. The remaining pieces arrive gradually (for example, when the promises for data resolve).
  5. After the JS loads, hydration runs - the page becomes interactive.

Example in React 18

Server

javascript
import { renderToPipeableStream } from "react-dom/server"; import express from "express"; import App from "./App"; const app = express(); app.get("/", (req, res) => { let didError = false; const stream = renderToPipeableStream(<App />, { onShellReady() { res.status(didError ? 500 : 200); res.setHeader("Content-Type", "text/html"); stream.pipe(res); // HTML starts to flow }, onError(err) { didError = true; console.error(err); }, }); });

Client

javascript
import { hydrateRoot } from "react-dom/client"; hydrateRoot(document.getElementById("root"), <App />);

renderToPipeableStream() is the streaming version of renderToString(), which lets React send HTML in chunks as they become ready.


Advantages of Streaming SSR

AdvantageDescription
Instant First PaintThe first part of the HTML (header, skeleton) is visible right away
Lower TTFB / LCPThe server doesn't wait for the render to finish to start responding
Smooth data loadingSections can be sent as soon as their data is ready
Suspense supportComponents with React.Suspense are automatically streamed later
Edge renderingThe stream works great on a CDN / edge server

What this looks like in the browser

javascript
[Server starts rendering App] ↓ Sends <html><head>...</head><body> ↓ Sends <Header> (ready instantly) ↓ While waiting for the API → sends <Skeleton /> ↓ When data arrives → replaces <Skeleton> with <Content /> ↓ Finishes the stream </body></html>

The user sees:

  1. The interface skeleton within milliseconds,
  2. Content loading in smoothly,
  3. No "blank loading screen".

Combined with React Suspense

Streaming SSR unlocks the full potential of Suspense:

javascript
<Suspense fallback={<Loading />}> <Comments /> </Suspense>
  • <Loading /> is rendered and streamed right away,
  • <Comments /> is added later, once the data has loaded, and React correctly inserts it in place without re-rendering the whole page.

Where Streaming SSR is used

  • Next.js 13+ (App Router) - used by default.
  • Remix, Astro, Qwik, SvelteKit - also implement streaming SSR.
  • Supported on Node.js, Deno, Edge (Cloudflare, Vercel).

Potential difficulties

ProblemDescription
CachingStreamed responses cannot simply be cached as finished HTML
Errors mid-streamIt's harder to roll back HTML that has already been sent
Debugging complexityIt's harder to see at which stage something went wrong
InfrastructureYou need servers that support streaming (HTTP chunked encoding)

Summary

Streaming SSR = "next-generation SSR":

  • Delivers HTML gradually, without waiting for the render to finish;
  • Lets the user see content sooner;
  • Works great with React 18 + Suspense;
  • But requires careful management of streams, caching, and errors.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.