Suspense Boundaries and Component-Level Streaming
Wrap slow data components in Suspense to stream them independently from the static shell.
Why Stream at the Component Level?
In the App Router, a page can contain both fast static content (header, nav, layout shell) and slow data-dependent content (a dashboard widget that hits a third-party API).
Without streaming, the whole page waits for the slowest fetch before anything renders. That hurts perceived performance.
Component-level streaming lets Next.js send the static shell immediately, then stream each slow part in as its data resolves. The tool that enables this is React's <Suspense> boundary.
The Blocking Problem
Here a single async Server Component awaits a slow query. Because the page awaits before returning JSX, the user sees nothing until the 2-second fetch finishes.
The fast parts of the page (title, layout) are held hostage by the slow part.
// app/dashboard/page.tsx
async function getStats() {
// simulates a slow 2s upstream call
await new Promise((r) => setTimeout(r, 2000));
return { revenue: 4200, orders: 87 };
}
export default async function DashboardPage() {
const stats = await getStats(); // blocks the WHOLE page
return (
<main>
<h1>Dashboard</h1>
<p>Revenue: {stats.revenue}</p>
<p>Orders: {stats.orders}</p>
</main>
);
}All lessons in this course
- Suspense Boundaries and Component-Level Streaming
- Crafting Meaningful loading.tsx and Skeletons
- Partial Prerendering: Static Shell, Dynamic Holes
- Streaming Pitfalls: Layout Shift and Waterfalls