Imagine that instead of sending the entire spaceship in one transport, you send it module by module -- the cockpit immediately, while navigation systems and cargo bays arrive in the background. That is exactly how Streaming SSR works -- the server starts sending HTML to the browser before the entire rendering is complete. And Edge Runtime moves this process closer to the user -- like relay stations distributed across the galaxy.
Traditional SSR generates the entire HTML on the server before sending it to the browser. The user waits for the slowest component:
1// Traditional SSR - user waits for EVERYTHING
2// Server: [render Header][render Data][render Footer] -> send everything
3// Time: 500ms + 2000ms + 100ms = 2600ms before they see anything
4
5// Streaming SSR - fragments sent immediately
6// Server: [Header ready] -> send! [Data ready] -> send! [Footer] -> send!
7// Time: 500ms to first fragment, rest arrives in backgroundReact 18 introduces
renderToPipeableStream as the successor to renderToString:1import { renderToPipeableStream } from 'react-dom/server';
2
3// Node.js server with Streaming SSR
4app.get('/', (req, res) => {
5 const { pipe } = renderToPipeableStream(
6 <App />,
7 {
8 bootstrapScripts: ['/main.js'],
9 onShellReady() {
10 // Shell is ready - send immediately
11 res.setHeader('Content-Type', 'text/html');
12 pipe(res);
13 },
14 onShellError(error) {
15 // Error in critical part - send fallback
16 res.statusCode = 500;
17 res.send('<h1>Server Error</h1>');
18 },
19 onAllReady() {
20 // Everything ready (useful for crawlers/SEO)
21 },
22 onError(error) {
23 console.error('Streaming error:', error);
24 }
25 }
26 );
27});onShellReady is called when the main "shell" of the page is ready -- everything outside <Suspense> boundaries. The user sees the page skeleton immediately.For Edge Runtime (Cloudflare Workers, Vercel Edge, Deno Deploy) we use
renderToReadableStream, which returns a Web Streams API:1import { renderToReadableStream } from 'react-dom/server';
2
3// Edge Runtime handler
4export default async function handler(request) {
5 const stream = await renderToReadableStream(
6 <App />,
7 {
8 bootstrapScripts: ['/main.js'],
9 onError(error) {
10 console.error('Edge streaming error:', error);
11 }
12 }
13 );
14
15 return new Response(stream, {
16 headers: { 'Content-Type': 'text/html' },
17 });
18}Difference between APIs:
renderToPipeableStream - Node.js Streams (Node.js runtime)renderToReadableStream - Web Streams API (Edge Runtime, Deno, browsers)<Suspense> defines streaming boundaries -- the server sends a fallback and later replaces it with the ready HTML:1function SpaceStationDashboard() {
2 return (
3 <div>
4 {/* Rendered immediately - part of "shell" */}
5 <Header title="Space Station Alpha" />
6 <Navigation />
7
8 {/* Each section streams independently */}
9 <Suspense fallback={<Skeleton type="missions" />}>
10 <MissionList /> {/* Data loads in 800ms */}
11 </Suspense>
12
13 <Suspense fallback={<Skeleton type="crew" />}>
14 <CrewStatus /> {/* Data loads in 1500ms */}
15 </Suspense>
16
17 <Suspense fallback={<Skeleton type="telemetry" />}>
18 <TelemetryData /> {/* Data loads in 2200ms */}
19 </Suspense>
20 </div>
21 );
22}
23
24// Streaming flow:
25// 1. [0ms] Server sends: Header + Navigation + 3x Skeleton
26// 2. [800ms] Server streams: MissionList (replaces Skeleton)
27// 3. [1500ms] Server streams: CrewStatus (replaces Skeleton)
28// 4. [2200ms] Server streams: TelemetryData (replaces Skeleton)Edge Runtime is an execution environment running on the "edge" of the network -- close to the user, on globally distributed CDN servers. Instead of one central server, your application runs in hundreds of locations:
1// next.config.js - Edge Runtime configuration in Next.js
2// In the page file:
3export const runtime = 'edge';
4
5// Or in a route handler:
6export const runtime = 'edge';
7
8export async function GET(request) {
9 // This code executes on the edge (close to user)
10 const userLocation = request.geo?.city || 'Unknown';
11
12 return new Response(
13 JSON.stringify({ message: 'Hello from edge!', location: userLocation }),
14 { headers: { 'Content-Type': 'application/json' } }
15 );
16}| Feature | Node.js Runtime | Edge Runtime | |---------|----------------|--------------| | Location | Single server region | Globally distributed | | Cold start | 200-500ms | 1-5ms | | Bundle size | No limit | ~1-4MB limit | | API | Full Node.js API | Limited (Web APIs) | | Streaming | renderToPipeableStream | renderToReadableStream | | Databases | Any | Edge-compatible (Planetscale, Turso) |
Streaming SSR naturally supports progressive hydration -- React hydrates components as they appear:
1// Progressive hydration flow:
2// 1. Server streams HTML -> browser displays
3// 2. JavaScript loads
4// 3. React hydrates components already in DOM
5// 4. New HTML fragments arrive from stream
6// 5. React hydrates them immediately upon receipt
7
8// Selective hydration - React prioritizes
9// components the user interacts with:
10function App() {
11 return (
12 <div>
13 {/* User clicked here - React hydrates this FIRST */}
14 <Suspense fallback={<Skeleton />}>
15 <InteractivePanel />
16 </Suspense>
17
18 {/* This waits in queue */}
19 <Suspense fallback={<Skeleton />}>
20 <HeavyDataTable />
21 </Suspense>
22 </div>
23 );
24}Streaming SSR and Edge Runtime are powerful tools for accelerating application delivery:
Streaming SSR changes application delivery like modular systems on a space station -- each module launches independently, the user does not wait for the whole thing.