DEV Community

Cover image for Streaming Server‑Side Rendering in React: A Learning Patterns Deep Dive
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Streaming Server‑Side Rendering in React: A Learning Patterns Deep Dive

Table of Contents


What Is Streaming Server‑Side Rendering?

Traditional server‑side rendering (SSR) waits until the entire HTML for a page is ready before sending it to the browser. Streaming SSR, by contrast, sends chunks of HTML as soon as they are generated.

This allows the browser to start parsing, painting, and hydrating the parts of the UI that are ready, improving Time to First Byte (TTFB) and Largest Contentful Paint (LCP).


Why Streaming?

From Learning Patterns:

  • Progressive Perceived Performance – users see something sooner, even on slow networks.
  • Reduced Blocking – slow data fetching or expensive computations in one part of the tree no longer block the entire document.
  • Back‑pressure Friendly – servers can push bytes as they become available, smoothing CPU spikes.

How React Implements Streaming

React 18 introduced two APIs inside react-dom/server:

API Environment Description
renderToPipeableStream() Node.js (streams) Emits HTML chunks into a Node Writable (e.g., an HTTP response).
renderToReadableStream() Edge runtimes / Deno Emits a web ReadableStream for platforms like Cloudflare Workers.

Key concepts from the book:

  1. Suspense Boundaries act as natural chunk boundaries.
  2. Client‑Side Hydration still happens, but progressively (hydrateRoot).
  3. Error Entrapment – errors inside a boundary stream a fallback instead of crashing the whole response.

Code Example – Express + React 18

Below is a minimal, production‑safe example that streams an app using renderToPipeableStream:

// server.ts
import express from "express";
import fs from "fs";
import { renderToPipeableStream } from "react-dom/server";
import App from "./App";
import React from "react";

const app = express();

app.get("*", (req, res) => {
  // Prepare HTML shell
  const { pipe } = renderToPipeableStream(<App url={{req.url}} />, {
    onShellReady() {
      // Set headers early for better streaming
      res.statusCode = 200;
      res.setHeader("Content-Type", "text/html");

      // Begin streaming!
      pipe(res);
    },
    onError(err) {
      console.error(err);
    },
  });

  // Abort the stream if the client disconnects
  res.on("close", () => pipe?.abort?.());
});

app.listen(3000, () =>
  console.log("➜  Server listening at http://localhost:3000 🚀")
);
Enter fullscreen mode Exit fullscreen mode
// App.tsx
import React, { Suspense } from "react";
import { Router } from "./Router";

export default function App({ url }: { url: string }) {
  return (
    <Suspense fallback={<p>Loading…</p>}>
      <Router initialUrl={{url}} />
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

Three Takeaways

  1. onShellReady fires when the initial HTML shell is ready (no data).
  2. Suspense fallbacks are sent immediately; React in the browser swaps them when data resolves.
  3. Abort logic prevents resource leaks if the connection drops.

Pros & Cons

Pros Cons
Performance Faster FCP & LCP; critical CSS/JS can stream early. Extra bytes from multiple script tags and fallbacks.
User Perception Immediate feedback reduces bounce rates. Janky experience if fallbacks are poorly designed.
Scalability Better CPU utilization under load; back‑pressure friendly. Longer connections per request can hurt throughput on very chatty pages.
Developer DX Works with Suspense; single render pass; minimal boilerplate. More moving parts (streaming + hydration), dev tools still maturing.
SEO Crawlers receive meaningful HTML sooner. Some bots don’t execute JS; ensure critical markup is in the shell.

Conclusion

Streaming SSR equips React apps with a progressive rendering pipeline that can dramatically enhance real‑world performance, especially on slower networks.

However, like every pattern in Learning Patterns, it shines only when aligned with product constraints:

  • Use it when you have data bottlenecks or large above‑the‑fold payloads.
  • Think twice when your pages are mostly static, or when infrastructure limits long‑lived connections.

Experiment, measure, and iterate—bytes don’t lie. 🏎️


References

  • Hallie, Lydia & Osmani, Addy. “Learning Patterns.” Chapter “Streaming Server‑Side Rendering”.
  • React 18 RFC: "Selective Hydration & Streaming".

Top comments (0)