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.
This means a slow database query for one widget no longer blocks the whole page — users see the shell and fast content immediately.
Example
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.
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.
// 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.
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