React Suspense Explained: Boundaries, Fallbacks, and Streaming

Suspense shows a fallback while a part of the tree loads its code or data, and coordinates which parts reveal together, with streaming SSR as the default in frameworks.

7 min read

React Suspense lets you show a loading fallback while a part of the tree finishes loading its code or data. A Suspense boundary wraps that part, swaps in a fallback, and reveals the real content when it is ready.

What activates a boundary

Suspense only waits for work React can see. It activates when a child lazy-loads code, reads a cached Promise with use, or is supplied by a Suspense-enabled framework. It does not activate for data fetched inside an Effect or an event handler.

The smallest boundary wraps a lazy component and names a fallback:

App.jsxApp.jsx
import { Suspense, lazy } from "react";
 
const MarkdownPreview = lazy(() => import("./MarkdownPreview.js"));
 
export default function Editor({ markdown }) {
  return (
    <Suspense fallback={<p>Loading preview...</p>}>
      <MarkdownPreview markdown={markdown} />
    </Suspense>
  );
}

The first time Editor renders, React requests the preview chunk and shows the fallback paragraph. When the chunk resolves, the preview replaces the fallback, and later renders reuse the cached component.

The full lazy story lives in code splitting in React with lazy and Suspense.

Reading a Promise with use works the same way. A component that calls use on a pending Promise suspends, the nearest boundary shows its fallback, and the content swaps in when the Promise resolves.

App.jsxApp.jsx
function Posts({ postsPromise }) {
  const posts = use(postsPromise);
  return <p>{posts.length} posts</p>;
}

The Promise must be cached so the same instance returns on every render, which is normally the framework's job. Without that cache, use sees a new Promise each render and suspends forever. In a Suspense-enabled framework such as Next.js, the framework caches the Promise for you, so components read data with use and the nearest boundary handles the wait.

Fallbacks and boundary placement

The fallback replaces the entire subtree inside the boundary while it loads. Put boundaries around sections, not every component, so loading states match what the user actually sees. A boundary that wraps nothing useful only adds a spinner.

Nested boundaries create a sequence. The outer one waits for everything, while an inner one lets faster content appear first.

App.jsxApp.jsx
<Suspense fallback={<PageSkeleton />}>
  <Header />
  <Suspense fallback={<PostsSkeleton />}>
    <Posts />
  </Suspense>
</Suspense>

Header renders as soon as the page shell is ready, while Posts stays behind its own skeleton. Each boundary is one reveal point, so you decide the order content appears in.

Everything inside one boundary is treated as a single unit. If one child suspends, the whole subtree swaps to the fallback and reveals together once ready. Nested boundaries split that into smaller reveal points.

Streaming and coordinated reveals

During streaming server rendering, React sends the page shell and the fallback first, then streams each boundary's HTML as it becomes ready and swaps the fallback out.

Streaming SSR with a Suspense boundary

The shell arrives first so the page paints fast, then each boundary fills in as its data finishes. A boundary with a lot of HTML can activate even when nothing inside it suspends, because the fallback fills in as the HTML arrives. React also reveals boundaries that become ready within the same short window together, so several sections do not pop in one at a time.

Keep stale content instead of hiding it

If an update would make already-visible content suspend, wrap the update in startTransition or use useDeferredValue. React then keeps the previous content on screen instead of flashing the fallback.

A transition waits long enough to avoid hiding revealed content, but it does not wait for every nested boundary. See useTransition explained for the pattern.

Status notes

The fallback behavior is stable. The defer prop, which lets a boundary show its fallback first even without suspending, is experimental in React 19.2.

The React 19.2 release also changed server rendering so boundaries that become ready close together reveal as one batch. Do not rely on defer in production until the React team stabilizes it.

What to learn next

A fallback is a loading state, and loading states should not make the page jump. See how to design loading states without layout shifts.

Rune AI

Rune AI

Key Insights

  • Suspense shows a fallback while code or data loads.
  • It activates for lazy, use, and framework data sources.
  • Nested boundaries create a loading sequence.
  • Streaming sends the shell first, then each boundary.
  • startTransition keeps stale content visible instead of flashing a fallback.
RunePowered by Rune AI

Frequently Asked Questions

Does Suspense wait for data fetched in useEffect?

No. Suspense only sees lazy code, a cached Promise read with use, or a Suspense-enabled framework's data source.

What is the difference between Suspense and an error boundary?

Suspense handles loading by showing a fallback until content is ready. An error boundary handles a render crash by showing a fallback and stopping that subtree.

Conclusion

Suspense shows a fallback while code or data loads and coordinates which parts reveal together. Place boundaries at your loading granularity, and use transitions to keep visible content stable.