Why streaming exists
Without streaming, the server has to wait for every piece of data before sending the first byte of HTML. With streaming, the framework sends the shell + cheap parts immediately, and streams the slower parts as their data resolves.
The two ways to stream
| Approach | Granularity |
|---|---|
loading.tsx | Whole route segment — framework wraps it in Suspense for you |
<Suspense fallback> | Per component — you choose what streams independently |
How it feels to the user
The page header and navigation appear instantly; the slow chart shows its skeleton; when the chart's data arrives, the skeleton swaps for the real content — without re-rendering the page above.
Place Suspense boundaries between units that have different latency and can remain understandable on their own. Make fallbacks roughly match final dimensions, keep the primary heading and actions outside slow regions, and stream in the order users can productively consume. When splitting slow boundaries, decide how much meaningful content appears first. Streaming loses its benefit if numerous fallbacks still leave the user unsure what is loading. Streaming changes perceived waiting, not the duration of a slow query. Skeletons around an unchanged waterfall can make the interface busier while server cost remains identical. Optimize the cause and the presentation as separate tasks.
Inject distinct delays into the shell, list, and chart. Record time to first byte, time to each region, and full completion under route-level and component-level boundaries. Check layout shift and announce loading state to assistive technology.