Using Suspense Boundaries to Control Streaming Granularity

Suspense boundaries split a page into independent chunks that stream separately. Learn how to place them for finer loading control.

7 min read

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.tsxApp.tsx
// 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.tsxApp.tsx
// 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.jsSuspense
ScopeThe whole pageAny component you wrap
SetupAdd a file to the segmentWrap the component yourself
Prefetched fallbackYesNo
Best forPages that show nothing without dataIsolating 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

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.
RunePowered by Rune AI

Frequently Asked Questions

How is Suspense different from loading.js?

loading.js is one fallback for the whole route. Suspense boundaries give you per-component control over what streams and when.

Can I use multiple Suspense boundaries on one page?

Yes. Each boundary streams independently, so a slow section does not delay the others.

Does Suspense work in Client Components?

Suspense itself is a React component and can wrap client content, but the streaming of async server work happens when a Server Component suspends.

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.