DEV Community

Cover image for React Suspense and Error Boundaries: How They Interact and How to Debug Failures
Srikar Phani Kumar Marti
Srikar Phani Kumar Marti

Posted on • Originally published at blog.mspk.me

React Suspense and Error Boundaries: How They Interact and How to Debug Failures

Ever had your React component throw an error inside a Suspense boundary and wondered why neither your Suspense fallback nor your Error Boundary’s fallback UI showed up?

I ran into this exact headache recently. I wrapped my lazy-loaded component with Suspense and an Error Boundary, expecting either a spinner or an error message. Instead, I got… nothing. Blank screen. No clues.

Turns out, Suspense and Error Boundaries have a subtle but important dance happening under the hood. Let me walk you through what’s really going on, why React sometimes seems to silently swallow errors or fallbacks, and how you can debug these tricky states.

The moment when Suspense and errors collide

Imagine this setup:

<Suspense fallback={<LoadingSpinner />}>
  <ErrorBoundary fallback={<ErrorMessage />}>
    <LazyComponent />
  </ErrorBoundary>
</Suspense>
Enter fullscreen mode Exit fullscreen mode

Your LazyComponent suspends while fetching data, so the spinner shows up. Great. But what if LazyComponent throws an error before the data arrives? You’d expect the ErrorBoundary to catch that and show the error message, right?

Not always.

In React’s commit phase , that is, when React applies changes to the DOM , if a component suspends, React bails out of rendering that tree and instead shows the Suspense fallback immediately. But if the same component throws an error (instead of suspending), React tries to find the nearest Error Boundary to handle it.

Here’s the catch: Suspense boundaries only catch suspensions. They don’t catch errors. Error Boundaries catch errors but don’t catch suspensions.

When a component inside Suspense throws an error, React’s update flow attempts to recover by unwinding to the Error Boundary. But if Suspense is the outer wrapper, React’s internal scheduling and commit phases can get tangled, causing neither fallback UI to appear.

What React does internally when errors happen inside Suspense

React uses a concept called "lanes" to prioritize updates and tracks whether a component tree is suspended or errored during render and commit phases.

When an error is thrown during rendering, React marks the fiber tree as "errored" and looks up the tree for the closest Error Boundary to handle it. If found, React schedules an error recovery update to replace the errored subtree with the Error Boundary’s fallback UI.

But if that errored subtree is inside a Suspense boundary that also suspended, React’s internal state can get confused:

  • Suspense boundaries expect a suspension to show fallback UI.
  • Error Boundaries expect an error to show fallback UI.
  • When both happen close together or in nested trees, React prioritizes suspensions and sometimes skips error handling in that render cycle.

This can cause your app to render nothing or show stale UI with no clear indication of what went wrong.

Common bugs that cause fallback UI to silently fail

1. Wrapping Suspense outside Error Boundaries

In the example earlier, Suspense wraps the Error Boundary. This means Suspense tries to handle suspensions first. If an error happens inside, Suspense doesn’t catch it , and the error bubbles up past Suspense.

But React’s commit phase handles Suspense boundaries differently than Error Boundaries, and because Suspense is outermost, React can get stuck showing no fallback.

Fix: Wrap Suspense inside the Error Boundary instead:

<ErrorBoundary fallback={<ErrorMessage />}>
  <Suspense fallback={<LoadingSpinner />}>
    <LazyComponent />
  </Suspense>
</ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

Now errors get caught by the Error Boundary before Suspense tries to handle suspensions.

2. Throwing errors asynchronously after suspending

If your component suspends by throwing a Promise, then later throws an error during commit or an effect, React’s update flow can get tricky. The Suspense fallback might show briefly, then the error triggers. But the Error Boundary’s fallback may not replace the Suspense fallback immediately, causing flickers or blank UI.

3. Nested Suspense boundaries with missing Error Boundaries

If you have multiple nested Suspense boundaries but no Error Boundary catching errors inside, React renders fallback UIs for suspensions but crashes silently on errors, often logging cryptic warnings.

Debugging strategies to untangle Suspense + Error Boundary issues

Inspect React DevTools Fiber tree

React DevTools now show Suspense and Error Boundary components explicitly. When your UI disappears or fallback UI doesn’t appear, inspect the fiber tree.

Look for:

  • Suspense boundaries marked as "suspended"
  • Error Boundaries marked as "errored"

If you see Suspense suspended but no error, it means React is still waiting on a Promise.

If you see Error Boundaries errored but no fallback UI, React might be skipping commit phases or your fallback component itself is throwing.

Add logging in error handlers

In your Error Boundary’s componentDidCatch or getDerivedStateFromError, log the error and info objects. This confirms the boundary is catching the error.

If you never see logs, React didn’t find your boundary.

Use React.unstable_DebugTracingMode (experimental)

In recent React versions, enabling this mode helps trace Suspense and error boundary interactions in the console, showing when boundaries suspend, resume, or error.

Check your error fallback components

Sometimes Error Boundary fallbacks throw errors themselves, causing React to unmount everything.

Wrap fallback UI in try/catch or keep it simple to isolate this.

How React’s update phases influence fallback rendering

React separates rendering into two steps:

  • Render phase: React calls your components to build a new fiber tree. This phase is pure , no side effects or DOM mutations.
  • Commit phase: React applies changes to the DOM, runs effects, and handles errors thrown here differently.

Suspensions happen during render phase, triggering React to delay committing the new UI and show Suspense fallback instead.

Errors can happen during render or commit.

If an error occurs during render, React tries to recover by rendering the nearest Error Boundary’s fallback in the next render phase.

If an error happens during commit (e.g., in an effect or lifecycle method), React unmounts the whole tree unless you have an error boundary.

Understanding this helps you see why sometimes the Suspense fallback shows, but the Error Boundary never does , or vice versa.

Practical takeaway: how to organize Suspense and Error Boundaries

  • Wrap Suspense inside Error Boundary if you want to catch errors thrown inside lazy or suspenseful components.

  • Keep your error fallback UI simple and robust to avoid cascading failures.

  • Avoid throwing errors asynchronously after suspensions if possible, or handle those with additional boundaries.

  • Test your boundaries by manually throwing errors and suspending promises to see the fallback behavior.

Final thought

React’s Suspense and Error Boundaries are powerful but can trip you up when they interact. When your fallback UI disappears or you see a blank screen, the problem is often in how these boundaries are nested and how React schedules updates.

With a little digging into React’s phases and careful boundary placement, you can tame these quirks and build reliable loading and error states. Next time you hit a ghostly blank screen, you’ll know exactly where to look.


Helpful learning resources

Top comments (0)