Trust is earned, not given

A different perspective

2026-01-13 · Projects

React.js, part 16: Suspense boundaries in production — streaming, nested fallbacks, and error recovery

Part 16from the React.js series · 20 parts in all

Placing one <Suspense> at the top of the app is the demo version. In production the interesting work is deciding where boundaries go, so that the page streams in useful pieces instead of one big spinnner, and so that a single failure does not take down a page that was mostly fine.

Streaming is a consequence of boundary placement

The server begins sending HTML as soon as the shell is ready; each boundary's fallback goes out immediately, and its real content streams in when it resolves. That means the boundary structure is the loading experience:

<Page>
  <Header />                            {/* no data: renders at once */}

  <Suspense fallback={<RecommendationsSkeleton />}>
    <Recommendations userId={id} />     {/* independent: streams in */}
  </Suspense>

  <Suspense fallback={<OrdersSkeleton />}>
    <Orders userId={id} />              {/* slow: does not block the rest */}
  </Suspense>
</Page>

Because Suspense boundaries are independent, one slow query delays only its own section. Put the boundary inside the layout around the slow child, never around the whole page, unless you genuinely want the whole page to wait.

Errors belong next to the boundary that hides the content

A thrown error during streaming must be caught, or it cancels the stream for everything after it. Error boundaries and Suspense are a matched pair — wrap the same subtree in both:

<ErrorBoundary fallback={<OrdersUnavailable />}>
  <Suspense fallback={<OrdersSkeleton />}>
    <Orders userId={id} />
  </Suspense>
</ErrorBoundary>

The design principle: a boundary is a promise about failure scope. If "recommendations are down" should not blank the order history, they must not share a boundary.

The hydration rules that still surprise people

Next: startTransition with async functions, and useEffectEvent as the escape hatch for "latest value" in an effect.