Streaming with Suspense

Wrap slow parts in <Suspense> to stream the rest of the page immediately while they load.

Syntax<Suspense fallback={<Spinner/>}>{slowComponent}</Suspense>

Next.js can stream a page: it sends the ready parts of the HTML right away and fills in slower sections as their data resolves. You control this with React's <Suspense> boundary and a fallback to show while waiting.

Request travels to the server, renders, and streams HTML back to the browser Browser requests /page Next.js renders on server Browser paints HTML 1. request 3. stream HTML 2. fetch + render chunks arrive as ready
Server Components render on the server and stream HTML so users see content sooner.

This means a slow database query for one widget no longer blocks the whole page — users see the shell and fast content immediately.

Example

Example · typescript
import { Suspense } from 'react';
import Feed from './feed';        // async Server Component (slow)
import Sidebar from './sidebar';  // fast

export default function Page() {
  return (
    <main>
      <Sidebar />
      <Suspense fallback={<p>Loading feed…</p>}>
        <Feed />
      </Suspense>
    </main>
  );
}

When to use it

  • A page header renders immediately while a slow API call for personalized recommendations streams in below it.
  • A product page streams the reviews section via Suspense so the price and add-to-cart button appear without waiting.
  • A dashboard wraps independent data widgets in separate Suspense boundaries so fast widgets appear before slow ones.

More examples

Suspense around slow section

The page title and price render immediately while Reviews streams in once its async data resolves.

Example · ts
import { Suspense } from 'react';
import Reviews from './Reviews';

export default function ProductPage() {
  return (
    <>
      <h1>Product Title</h1>
      <p>Price: $99</p>
      <Suspense fallback={<p>Loading reviews...</p>}>
        <Reviews />
      </Suspense>
    </>
  );
}

Async component inside Suspense

The async component triggers the nearest Suspense boundary until its fetch resolves.

Example · ts
// components/Reviews.tsx
export default async function Reviews() {
  const reviews = await fetch('/api/reviews').then(r => r.json());
  return (
    <ul>
      {reviews.map((r: any) => <li key={r.id}>{r.text}</li>)}
    </ul>
  );
}

Multiple parallel Suspense boundaries

Separate boundaries let each widget stream independently — the faster one appears first.

Example · ts
import { Suspense } from 'react';
import RevenueChart from './RevenueChart';
import RecentOrders from './RecentOrders';

export default function Dashboard() {
  return (
    <div className="grid grid-cols-2 gap-4">
      <Suspense fallback={<p>Loading chart...</p>}>
        <RevenueChart />
      </Suspense>
      <Suspense fallback={<p>Loading orders...</p>}>
        <RecentOrders />
      </Suspense>
    </div>
  );
}

Discussion

  • Be the first to comment on this lesson.