← Debugging Guides

阅读中文版本

React — A Component Suspended with No Fallback UI

Fix React suspended without a fallback error and hydration-time suspension flashes, place Suspense boundaries correctly, and use startTransition for urgent updates.

intermediatedebuggingreactsuspenselazy-loading

Symptoms

  • Error: A component suspended while rendering, but no fallback UI was specified, or while responding to synchronous input
  • The whole page blanks and remounts when one lazy child starts loading
  • Client-side navigation replaces the entire route because no boundary isolates the loading part

Possible causes

  • A lazy component or data-dependent component suspends with no Suspense boundary above it
  • A boundary exists but was placed above the whole page, so the fallback replaces everything
  • Suspension happens while handling synchronous input without startTransition, blocking the update
  • During hydration, a suspended child without a boundary forces the whole tree back to client rendering

Diagnosis steps

Read the error scope

dev console: the error text distinguishes render-time missing fallback from synchronous-input suspension

Interpretation

The two variants need different fixes: add a boundary versus wrap the update in a transition.

Map boundary placement

grep -Rn "<Suspense" src/ | head; grep -Rn "lazy(" src/ | head

Interpretation

Each lazy or suspending component needs the nearest boundary to be tight around it, not around the page shell.

Classify update urgency

grep -Rn "useTransition\|startTransition" src/ | head -5

Interpretation

Input-driven updates that trigger suspension must be transitions; urgent renders cannot wait for fallbacks by design.

Root cause

When a component suspends, React walks up to the nearest Suspense boundary and renders its fallback instead. Without any boundary, or with a boundary placed too high, either React throws the missing-fallback error or the entire subtree hides behind one spinner — the page blanks for a fragment load.

Fix

Place Suspense boundaries tightly around each suspending unit (lazy route segment, data region), provide meaningful scoped fallbacks, wrap non-urgent updates that suspend in startTransition so the old UI stays interactive, and keep the shell outside any boundary.

Risk note

Fallbacks that reset scroll or unmount siblings turn loading into layout churn. Design fallbacks to preserve geometry.

Verify the fix

Lazy-loading a fragment keeps the shell and sibling regions rendered; input that triggers data loading stays responsive with stale content until the new content commits.

Symptom walkthrough

The error names the missing piece:

Error: A component suspended while rendering, but no fallback UI
was specified. Add a <Suspense boundary=...> around it.

The silent variant is worse UX with no error: one boundary wraps the whole app, so a lazy settings panel loading blanks the entire page behind one spinner. The map to draw is which components suspend (lazy imports, data hooks) and where the boundaries sit relative to them. Boundary far above the suspending child equals page-wide fallback; boundary tight around it equals scoped spinner with the shell intact.

Fix in practice

Tight boundaries around each suspending unit:

function ProfilePage() {
  return (
    <Shell>                       {/* outside any boundary: always rendered */}
      <Header />
      <Suspense fallback={<TimelineSkeleton />}>
        <Timeline />               {/* suspends on data */}
      </Suspense>
      <Suspense fallback={<RecoSkeleton />}>
        <Recommendations />        {/* suspends independently */}
      </Suspense>
    </Shell>
  );
}

Input-driven updates that suspend go through a transition:

const [isPending, start] = useTransition();

function onSelect(tab) {
  start(() => {
    setTab(tab);   // suspending data fetch marked non-urgent
  });
}
// old tab stays interactive and visible until the new one commits

Design the fallbacks to hold geometry — skeletons matching the loaded layout prevent the load itself from causing layout shift and scroll resets.