Streaming with Suspense

Without Suspense, users see blank screens while data loads. Each database query or API call blocks the entire page from rendering. With Suspense, you design what users see during loading—skeleton placeholders that communicate progress, not emptiness.

Each Suspense boundary is a UX decision: "What should users see while this section loads?" And importantly: "Should these sections wait for each other, or load independently?"

Independent Streaming

Separate boundaries let sections stream in parallel. The tabs can appear while posts are still loading:

import { Suspense } from 'react'; export default function DashboardPage({ searchParams }) { return ( <div> <Suspense fallback={<PostTabsSkeleton />}> <PostTabs /> </Suspense> <Suspense fallback={<SortButtonSkeleton />}> <SortButton /> </Suspense> <Suspense fallback={<PostListSkeleton />}> <PostList searchParams={searchParams} /> </Suspense> </div> ); }

Each component fetches its own data. As each resolves, it streams in and replaces its skeleton—independently of the others.

Boundary Placement Strategy

How you group components inside Suspense boundaries affects perceived performance:

ScenarioStrategyEffect
Related contentSingle boundaryLoad together, show together
Independent sectionsSeparate boundariesStream in parallel as ready
Critical UI (header, nav)Outside boundariesAlways visible immediately
Heavy contentOwn boundaryDon't block lighter content

Nested Boundaries

Suspense boundaries can nest. Inner boundaries resolve first:

<Suspense fallback={<PageSkeleton />}> <Header /> <Suspense fallback={<ContentSkeleton />}> <Content /> </Suspense> </Suspense>

The outer skeleton shows briefly, then the header appears, then content streams in. Users see progressive disclosure.

Transitions Keep Content Visible

Here's a crucial distinction: Suspense fallbacks appear on initial load. During subsequent navigations wrapped in startTransition, React keeps existing content visible instead of showing fallbacks again.

// Using Link or router.push - content stays visible during navigation <Link href="/dashboard?filter=drafts">Drafts</Link> // The PostList re-fetches but old content shows until new data arrives

This is why filtering and sorting feel instant even though data is refetching—the skeleton only shows on first load. For subsequent interactions, the current list stays visible with a subtle pending state.

November 2, 2025Updated March 1, 2026349 words