← Back to Docs
Recipe

Streaming SSR Patterns

Deliver meaningful content immediately while the rest of the page streams in.

The Problem

Traditional SSR blocks the entire response until every data dependency resolves. A single slow database query or external API call delays the full HTML payload, leaving users staring at a blank screen. Streaming SSR solves this by flushing chunks of HTML as they become ready.

Core Pattern

Wrap slow components in React Suspense boundaries. The server sends a fallback immediately, then streams the resolved component once its async data finishes. Next.js App Router handles this natively — no extra configuration required.

// page.tsx
import { Suspense } from 'react';
import { SlowWidget } from './slow-widget';
import { Skeleton } from './skeleton';

export default function Page() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<Skeleton />}>
        <SlowWidget />
      </Suspense>
    </main>
  );
}

Async Server Component

Mark the component as async and fetch data directly. No useEffect, no client-side state. The server resolves the promise and streams the rendered output.

// slow-widget.tsx
export async function SlowWidget() {
  const data = await fetch('https://api.example.com/analytics', {
    next: { revalidate: 60 },
  }).then(res => res.json());

  return (
    <div className="p-4 border rounded">
      <h2>Active Users</h2>
      <p className="text-2xl font-bold">{data.active}</p>
    </div>
  );
}

Loading States

Design skeleton fallbacks that match the shape of the resolved component. This prevents layout shift when the streamed content replaces the fallback. Use fixed heights and widths to reserve space.

// skeleton.tsx
export function Skeleton() {
  return (
    <div className="animate-pulse p-4 border rounded">
      <div className="h-4 w-32 bg-gray-700 rounded" />
      <div className="mt-3 h-8 w-16 bg-gray-700 rounded" />
    </div>
  );
}

Nested Streaming

Compose multiple Suspense boundaries for independent data sources. Each boundary streams independently — the fastest data renders first. Users see a progressively populated page instead of waiting for the slowest query.

Edge Considerations

Streaming works best on Node.js runtimes. Edge runtime supports streaming but has tighter limits on duration and payload size. For long-running streams, prefer the Node.js runtime and set appropriate maxDuration in your route segment config.