Overview

A <Suspense> boundary renders its fallback while any descendant suspends: a component calling use(promise), a lazy() component, an async Server Component, or a Suspense-enabled data library. Transitions are the partner primitive: they keep the old UI on screen while the new UI prepares. Boundary placement decides what streams, what blocks, and how much of the page hydrates early.

Place boundaries where the user expects a wait

Add a boundary around each independent slow region and nowhere else. One boundary around the whole page makes the slowest fetch gate everything.

<Header />
<Suspense fallback={<TableSkeleton />}><ProductTable /></Suspense>
<Suspense fallback={<SidebarSkeleton />}><Recommendations /></Suspense>

Make fallbacks occupy the final layout

A fallback should take the same box as the resolved content, so nothing jumps when data arrives. Use a skeleton for content blocks and set explicit dimensions on media. This avoids layout shift; see core-web-vitals.

Nest boundaries to control reveal order

An inner boundary hides only its own subtree. Nest from the page shell inward so the header, then the body, then comments appear as each is ready.

<Suspense fallback={<PageSkeleton />}>
  <ArticleHeader />
  <Suspense fallback={<BodySkeleton />}>
    <ArticleBody />
    <Suspense fallback={<CommentsSkeleton />}><Comments /></Suspense>
  </Suspense>
</Suspense>

React 19.2 batches reveals of server-streamed boundaries to match client behavior, so adjacent regions may appear together.

Use useTransition for updates you trigger

Wrap the state update in startTransition and React keeps the current content interactive instead of swapping in a fallback. Transitions accept async functions (Actions) in React 19, and isPending covers the whole Action.

const [isPending, startTransition] = useTransition();
const selectTab = (id: string) => startTransition(() => setActiveTab(id));

Use isPending for subtle feedback such as a dimmed panel. Without a transition, a suspending update unmounts the content and shows the fallback.

Use useDeferredValue for values you do not control

Use useDeferredValue when a value arrives from props or context and drives an expensive render; React paints with the old value first, then catches up. Use useTransition when you own the setter.

const deferredQuery = useDeferredValue(query);
return <ExpensiveResults query={deferredQuery} />;

Animate Transition and Suspense updates with <ViewTransition>

<ViewTransition> (stable in React 19.3) animates elements that enter, exit, update, or move when the change happens inside a Transition, a Suspense reveal, or a deferred value. It does not animate plain setState updates. Use addTransitionType to choose the animation by cause, for example forward versus back navigation.

Stream HTML and hydrate selectively

In a streaming framework the server sends the shell first and each boundary as its data resolves. React hydrates the shell immediately and each region as it arrives, so a boundary near the root delays interactivity for everything inside it. Default to leaf-ward boundaries and move one up only when the parent depends on the child’s data.

Pair every Suspense region with an error boundary

Suspense handles loading, not failure. A rejected promise surfaces at the nearest error boundary, so wrap each region in both. See react-error-boundaries, and nextjs for the loading.tsx and error.tsx file convention.