Skip to content

Streaming and Suspense

Streaming lets the server send the ready parts of a page — the static shell — immediately, then push the slower parts down the same connection as they finish rendering.

Without streaming, the server holds the whole response until every await resolves, so the slowest piece of data sets the time to first byte. Streaming breaks the page into chunks: the shell (layout, headings, anything not waiting on data) leaves the server first, and slow regions follow as they resolve.

The browser paints the shell right away, so the user sees a real page while data is still loading in the background.

loading.tsx is an automatic Suspense boundary

Section titled “loading.tsx is an automatic Suspense boundary”

Drop a loading.tsx beside a page.tsx and Next.js wraps that whole segment in a Suspense boundary for you. While the page awaits its data, the loading UI shows instantly.

// app/feed/loading.tsx — shown instantly while page.tsx awaits its data
export default function Loading() {
return <p>Loading your feed…</p>;
}

You write no <Suspense> yourself here — the file convention is the boundary. The segment streams in once its data is ready.

Manual Suspense streams just one slow part

Section titled “Manual Suspense streams just one slow part”

When only one region of the page is slow, wrap that region in <Suspense> so the rest of the page renders immediately and only the slow component streams in.

app/feed/page.tsx
import { Suspense } from 'react';
async function SlowComments() {
const comments = await fetch('https://api.example.com/comments', {
cache: 'no-store',
}).then((r) => r.json());
return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>;
}
export default function FeedPage() {
return (
<section>
<h1>Your feed</h1>
<p>This heading and text ship in the shell, immediately.</p>
<Suspense fallback={<p>Loading comments…</p>}>
<SlowComments />
</Suspense>
</section>
);
}

The heading paints at once; SlowComments streams in and replaces its fallback when the fetch resolves.

Why streaming improves perceived performance

Section titled “Why streaming improves perceived performance”

Streaming decouples “first paint” from “slowest data”. Two wins fall out of that:

  • Better TTFB — the shell leaves the server without waiting on any data request, so the first byte arrives fast.
  • Better perceived performance — the user reads a real layout while slow regions fill in, instead of staring at a blank screen until everything is ready.
flowchart LR
  A[Request] --> B[Server sends static shell now]
  B --> C[Browser paints shell, fast TTFB]
  D[Slow async component resolves] --> E[Server streams the chunk]
  E --> F[Suspense fallback swaps to real content]
Shell first, then stream the slow parts
What is sent to the browser first when a page streams?
What does a loading.tsx file create for its segment?
When should you reach for a manual <Suspense> boundary?
Why does streaming improve time to first byte?