Streaming keeps pages responsive when some data is slow, and loading boundaries make navigations feel instant.
When a Server Component awaits slow data, nothing below it can render until the data arrives. Streaming splits the page into chunks so the server sends what's ready first and the rest as it resolves, over the same response.
Two ways to create streaming boundaries:
- `loading.tsx` in a route segment wraps that segment's page in a React
<Suspense>boundary automatically. Its component shows immediately on navigation (the layout stays interactive) and is replaced by the page when it has rendered. It also makes navigations to dynamic routes feel instant, because the loading state can be prefetched. - `<Suspense fallback={...}>` around individual components gives finer control: the static parts and other sections render immediately, and each suspended component streams in when its data is ready.
Design meaningful fallbacks: skeletons that match the final layout avoid jumps. Place boundaries around independent slow parts so they load in parallel rather than one blocking everything.
Streaming works with use() too: start a fetch in a Server Component without awaiting, pass the promise to a Client Component, and read it there inside Suspense.
Search engine bots that can't handle streaming get the fully rendered page instead; Next.js detects them. With Cache Components, Suspense boundaries also mark the request-time "holes" in a prerendered page, which you will learn next.
Example
// app/dashboard/loading.tsx - instant fallback for the whole segment
export default function Loading() {
return <p className="animate-pulse">Loading dashboard...</p>
}
// app/dashboard/page.tsx
import { Suspense } from 'react'
async function Revenue() {
const res = await fetch('https://api.example.com/revenue') // slow (2 s)
const { total } = await res.json()
return <p>Revenue: ${total}</p>
}
async function LatestOrders() {
const orders: { id: string }[] = await (await fetch('https://api.example.com/orders')).json()
return <ul>{orders.map((o) => <li key={o.id}>{o.id}</li>)}</ul>
}
export default function Dashboard() {
return (
<>
<h1>Dashboard</h1> {/* sent immediately */}
<Suspense fallback={<p>Loading revenue...</p>}><Revenue /></Suspense>
<Suspense fallback={<p>Loading orders...</p>}><LatestOrders /></Suspense>
</>
)
}Remember
loading.tsxwraps a segment's page in Suspense automatically<Suspense>around slow components streams them independently- Separate boundaries let slow parts load in parallel without blocking the page
Watch out for
- One Suspense boundary around the whole page, so the fastest content waits for the slowest.