Streaming & Suspense
Stream UI progressively using React Suspense boundaries and loading files
Overview
Streaming lets the server send HTML in chunks so users see content before all data is ready. In the App Router you enable it with React <Suspense> boundaries or a loading.tsx file. Slow data fetches render a fallback first, then swap in the real content as it resolves, improving perceived performance.
Syntax / Usage
Wrap a slow async component in <Suspense> and provide a fallback. The rest of the page streams immediately.
// app/page.tsx
import { Suspense } from 'react'
export default function Page() {
return (
<main>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading revenue…</p>}>
<Revenue />
</Suspense>
</main>
)
}
async function Revenue() {
const data = await getRevenue() // slow
return <RevenueChart data={data} />
}
Examples
Use loading.tsx for instant route-level loading UI:
// app/dashboard/loading.tsx
export default function Loading() {
return <DashboardSkeleton />
}
Stream multiple independent sections so each appears as soon as it is ready:
import { Suspense } from 'react'
export default function Page() {
return (
<>
<Suspense fallback={<CardSkeleton />}>
<LatestOrders />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<TopCustomers />
</Suspense>
</>
)
}
Common Mistakes
- Awaiting slow data in the page component itself, which blocks the whole route
- Wrapping the entire page in one boundary instead of isolating slow parts
- Forgetting a
keyor stable fallback, causing layout shift when content loads - Expecting
loading.tsxto apply to nested layouts it does not wrap - Using Suspense for client-only fetching where it will not stream from the server
See Also
nextjs-data-fetching server-components app-router