Stream behind Suspense
Await slow data inside a Suspense boundary so the shell paints first.
From vercel-react-best-practices, rule async-suspense-boundaries.
One slow query, blank page
Drag the latency; release to reload.
await in PageBad
Waiting for server…
Acme
Revenue
Jul$42.1k
Aug$47.8k
Sep$51.3k
First paint –Data –
await in <Suspense>Good
Waiting for server…
Acme
Revenue
Jul$42.1k
Aug$47.8k
Sep$51.3k
First paint –Data –
export default function Page() {
return (
<Shell>
<Suspense fallback={<RevenueSkeleton />}>
<Revenue />
</Suspense>
</Shell>
)
}
async function Revenue() {
const data = await fetchData()
return <RevenueTable rows={data.rows} />
}
Two readers, one promise
Load both a few times; count requests and reveals.
Each component fetchesBad
<DataDisplay>
Northwind$4,200
Globex$3,150
Initech$2,500
<DataSummary>
$9,850
3 open orders
Requests –Reveals –
Shared promise + use()Good
<DataDisplay>
Northwind$4,200
Globex$3,150
Initech$2,500
<DataSummary>
$9,850
3 open orders
Requests –Reveals –
function Page() {
const dataPromise = fetchData()
return (
<Suspense fallback={<Skeleton />}>
<DataDisplay dataPromise={dataPromise} />
<DataSummary dataPromise={dataPromise} />
</Suspense>
)
}
function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise)
return <div>{data.summary}</div>
}
Wrong-size fallbacks shift
Change rows returned; watch the Pay button move.
Spinner fallback, 40pxBad
Invoices
Pay invoice
Shift –Score –
Skeleton of 4 rowsGood
Invoices
Pay invoice
Shift –Score –
function InvoicesSkeleton() {
return (
<ul className="flex flex-col gap-1.5 min-h-[130px]">
{Array.from({ length: 4 }, (_, i) => (
<li key={i} className="h-7 rounded bg-muted" />
))}
</ul>
)
}
<Suspense fallback={<InvoicesSkeleton />}>
<Invoices />
</Suspense>
Why it matters, pros and cons
Why it matters
A top-level await blocks the whole response. Suspense streams the shell first.
Pros
- First paint ignores the slowest query
- The shell is usable while data loads
- One shared promise, one request
Cons
- Data that decides layout, redirects or 404s
- Fast queries: a flashing skeleton is worse
- A wrong-height fallback shifts the layout