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.
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 suspensionInterpretation
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/ | headInterpretation
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 -5Interpretation
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.