Open-Source Wikis

/

React

/

Features

/

Suspense

facebook/react

Suspense

A Suspense boundary catches asynchronous "I'm not ready" signals from its descendants and shows a fallback in their place until they are. The implementation is one of the most cross-cutting features in the runtime, touching the reconciler, the streaming server (Fizz), the Flight client, and the DevTools backend.

The core trick

Inside a render, a component can throw a thenable (throw promise) to signal "I'm not done; come back when this resolves." The reconciler's ReactFiberThrow.js distinguishes thenables from Errors: thenables propagate up to the nearest <Suspense> boundary, errors propagate up to the nearest error boundary.

graph TD
  Render[Render Foo] -->|throw promise| Throw[ReactFiberThrow.handleThrow]
  Throw -->|walk up tree| Suspense[Find nearest Suspense fiber]
  Suspense -->|register thenable| Retry[ReactFiberThenable: trackUsedThenable]
  Suspense -->|mark fiber DidCapture| Reconciler
  Reconciler -->|render fallback| Commit
  Promise[promise resolves] -->|wakeable.then| Retry
  Retry -->|markRootUpdated retry lane| RootScheduler
  RootScheduler -->|reschedule| Reconciler
  Reconciler -->|re-render Foo| Done[succeeds → unsuspend]

use(promise) (ReactFiberHooks.useThenable) is the canonical way to throw a thenable. Most libraries (TanStack Query, Apollo, Relay) wrap their fetches in use() so they participate in Suspense automatically.

Components and files

Component File
<Suspense> exported from packages/react/src/ReactClient.js (just a symbol — REACT_SUSPENSE_TYPE); the runtime behavior is in the reconciler.
Suspense fiber tag packages/react-reconciler/src/ReactWorkTags.js (SuspenseComponent).
Boundary state packages/react-reconciler/src/ReactFiberSuspenseComponent.js
Boundary context packages/react-reconciler/src/ReactFiberSuspenseContext.js
Throw handling packages/react-reconciler/src/ReactFiberThrow.js
Thenable tracking packages/react-reconciler/src/ReactFiberThenable.js
Hidden context (subtree visibility) packages/react-reconciler/src/ReactFiberHiddenContext.js
SSR-side boundary handling packages/react-server/src/ReactFizzServer.js
Flight-side boundary handling packages/react-server/src/ReactFlightServer.js

Retries

When a thrown thenable resolves, its .then(...) callback calls pingSuspendedRoot (in ReactFiberWorkLoop.js), which:

  1. Marks the boundary's lane as no longer suspended on the root (markRootPinged).
  2. Schedules a new render at a RetryLane priority — typically lower than user input but higher than idle.
  3. The next tick re-renders the boundary; use(promise) now returns synchronously and the render succeeds.

RetryLanes are a separate slice of the priority space because they shouldn't block higher-priority interactive updates but should still come before idle work.

Streaming SSR (Fizz) and Suspense

The streaming SSR engine in packages/react-server/src/ReactFizzServer.js is built around Suspense. Each boundary becomes one or more segments — chunks of HTML that may stream out of order:

sequenceDiagram
  participant App
  participant Fizz as ReactFizzServer
  participant Stream

  App->>Fizz: renderToReadableStream(&lt;App/&gt;)
  Fizz->>Stream: emit shell HTML
  Fizz->>Fizz: hit <Suspense> with thrown promise
  Fizz->>Stream: emit fallback inline (with placeholder id)
  Fizz->>Fizz: keep working on other branches
  Fizz->>Fizz: when promise resolves, render the boundary content
  Fizz->>Stream: emit completed boundary HTML in a <template>
  Fizz->>Stream: emit a <script> snippet that swaps fallback for content

The "tiny inline runtime" that does the swap is generated by scripts/rollup/generate-inline-fizz-runtime.js and inlined at compile time into Fizz output.

prerender and resume extend Fizz with a postpone() primitive — when a boundary postpones, Fizz emits a marker and stops; later, a different request can pick up where the prerender left off. This is the mechanism behind React 19.2's partial pre-rendering.

Selective hydration

On the client, hydration walks the existing DOM and matches it to a fresh React tree. With streaming SSR, boundaries arrive over time. packages/react-reconciler/src/ReactFiberHydrationContext.js orchestrates:

  • Boundaries hydrate at low priority by default.
  • A user click on a still-unhydrated boundary bumps its priority to SyncHydrationLane.
  • Boundaries can resuspend during hydration (the SSR HTML mismatched the client tree); when they do, ReactFiberHydrationDiffs.js produces the rich "Server: ... Client: ..." mismatch error.

SuspenseInstance (a host-config concept) is the host platform's representation of a not-yet-ready boundary. For the DOM, it's a comment node like <!--$?--> placed by Fizz; the reconciler walks past it during hydration.

Activity (<Activity>) and Suspense

<Activity mode="hidden"> is essentially "Suspense's evil twin": it hides a subtree and tears down its effects, but preserves state. Code in packages/react-reconciler/src/ReactFiberOffscreenComponent.js.

The two cooperate:

  • A subtree inside an <Activity mode="hidden"> doesn't suspend the visible tree.
  • A revealed <Activity> may need to flush previously-deferred work; the reconciler handles this via OffscreenLane.

View transitions and Suspense

<ViewTransition> integrates with Suspense in two ways:

  1. A boundary that resolves during an active view transition has its content's reveal coordinated with the transition's animation phase.
  2. View transitions can themselves be gesture-driven, with the gesture scheduler (ReactFiberGestureScheduler.js) coordinating with retry lanes.

The full integration is still landing — code in ReactFiberCommitViewTransitions.js and ReactFiberApplyGesture.js.

Where to start when a Suspense bug shows up

  1. Reproduce in packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js. Most Suspense bugs are diagnosable against react-noop-renderer rather than react-dom.
  2. Add Scheduler.log(...) calls in ReactFiberThrow.handleThrow and the Suspense-related code paths in ReactFiberBeginWork.js and ReactFiberCompleteWork.js.
  3. For SSR/streaming bugs, reproduce in fixtures/ssr2/ or one of the Flight fixtures, and tail the byte stream.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Suspense – React wiki | Factory