Using Suspense boundaries in Next.js is how you decide which parts of a page wait for data. A boundary wraps an async component so only that component waits, while everything outside it renders and streams immediately.
The boundary is the unit of streaming, so where you put it decides how much of the page arrives together.
// app/dashboard/page.tsx
import { Suspense } from "react";
import { PostFeed } from "./post-feed";
export default function Dashboard() {
return (
<section>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading feed</p>}><PostFeed /></Suspense>
</section>
);
}The heading and shell render at once, while the feed streams in later. The rest of the page never waits for the feed.
How a component suspends
A Server Component suspends when it awaits data inside a boundary. React pauses that component, renders the nearest fallback, and resumes it when the promise settles. You never write the suspension yourself; adding an await inside a boundary is all it takes.
A rejected promise is an error, not a suspension. It propagates to the nearest error boundary, such as an error file, instead of leaving the fallback on screen.
Why granularity matters
Without a boundary, one slow component suspends the entire page and nothing renders until it finishes. Each boundary you add isolates a slow piece, so unrelated content reaches the browser first.
With Cache Components enabled in Next.js 16, this is not just a performance question. Uncached or runtime data with no boundary above it fails the build with a blocking route error, because the framework could not produce a static shell for that route.
Granularity is about placing the boundary at the right height. Too high and one slow piece blocks everything below it. Too low and you manage many tiny fallbacks for no visible benefit.
// app/dashboard/page.tsx
import { Suspense } from "react";
import { PostFeed } from "./post-feed";
import { Weather } from "./weather";
export default function Dashboard() {
return (
<section>
<Suspense fallback={<p>Loading feed</p>}><PostFeed /></Suspense>
<Suspense fallback={<p>Loading weather</p>}><Weather /></Suspense>
</section>
);
}Each boundary streams independently, so the feed and weather resolve in parallel. A slow weather request no longer delays the feed.
Nested boundaries
Boundaries can nest. An outer boundary with a coarse fallback covers a whole section, while an inner boundary covers one slow card inside it.
The nearest boundary wins for the component inside it, so only the innermost fallback around that component is shown. The result is a progressive reveal: the outer fallback clears first, then the inner one, as each level resolves.
Designing a fallback
A fallback should look like a lighter version of the real content, not a blank box. Use a skeleton with the same dimensions so the swap does not shift the layout. Keep the fallback free of its own data fetching, so it renders instantly instead of suspending a second time.
Suspense vs loading.js
loading.js gives the whole route one fallback, and it is actually a Suspense boundary Next.js adds around the page for you. When you write Suspense yourself, you choose the boundary size and the fallback per component.
| loading.js | Suspense | |
|---|---|---|
| Scope | The whole page | Any component you wrap |
| Setup | Add a file to the segment | Wrap the component yourself |
| Prefetched fallback | Yes | No |
| Best for | Pages that show nothing without data | Isolating the slow parts |
Prefer a boundary close to the slow work, and keep loading.js for the case where the page has nothing to show until its data arrives. For the route-level file, see loading.js, and for how the streamed HTML reaches the browser see streaming in Next.js.
Rune AI
Key Insights
- A Suspense boundary isolates slow async work from the rest of the page.
- Content outside the boundary streams immediately.
- Multiple boundaries stream independently of each other.
- Use loading.js for the route shell, Suspense for fine-grained control.
- Each boundary needs a fallback shown while its content loads.
Frequently Asked Questions
How is Suspense different from loading.js?
Can I use multiple Suspense boundaries on one page?
Does Suspense work in Client Components?
Conclusion
Suspense boundaries isolate slow work so only that piece waits for data. Place a boundary around each slow component and the rest of the page streams immediately, independent of the slow sections.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.