React Suspense
Coordinate asynchronous loading states declaratively using Suspense boundaries and lazy-loaded component splitting.
TL;DR
- Coordinate asynchronous data loading states declaratively using modern React
Suspenseboundaries. - Split heavy client application bundles on demand using dynamic
React.lazy. - Display polished skeleton placeholders using the
fallbackprop.
Code Splitting with React.lazy
Dynamic Component ImportImport component on demand when first rendered.
const HeavyChart = lazy(
() => import('./HeavyChart')
);Suspense Boundary WrapperProvide skeleton fallback during chunk download.
export function Dashboard() {
return (
<Suspense fallback={<CardSkeleton />}>
<HeavyChart />
</Suspense>
);
}Named Export AdapterAdapt named export to default export for lazy.
const Analytics = lazy(() =>
import('./Tools').then((m) => ({
default: m.Analytics,
}))
);Coordinating Suspense Boundaries
Nested Granular BoundariesRender fast components while slow ones load.
<Suspense fallback={<HeaderSkeleton />}>
<Header />
<Suspense fallback={<FeedSkeleton />}>
<Feed />
</Suspense>
</Suspense>Parallel Loading with BoundariesLoad sibling widgets simultaneously in parallel.
<RowView>
<Suspense fallback={<CardSkeleton />}>
<WidgetA />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<WidgetB />
</Suspense>
</RowView>Combined Boundary and Error BoundaryHandle loading and error states together cleanly.
<ErrorBoundary FallbackComponent={ErrorCard}>
<Suspense fallback={<CardSkeleton />}>
<DataView />
</Suspense>
</ErrorBoundary>Suspense with React 19 Data
React 19 use HookRead promise directly inside component with use.
function UserProfile({ promise }: Props) {
const user = use(promise);
return <CardTitle>{user.name}</CardTitle>;
}Suspense Preloaded PromiseTrigger data request before rendering component.
const userPromise = fetchUser(id);
return (
<Suspense fallback={<CardSkeleton />}>
<UserProfile promise={userPromise} />
</Suspense>
);Streaming Server ComponentStream slow content chunks down as ready.
<Suspense fallback={<ListSkeleton />}>
<AsyncServerReviews />
</Suspense>Suspense Best Practices
Skeleton UI AlignmentMatch skeleton dimensions to final rendered output.
// Prevent layout shift with exact skeleton sizes
<Box style={{ height: 240, width: '100%' }} />Avoid Spinner CascadesGroup related components under a single boundary.
// Better UX: Single boundary for coherent screen
<Suspense fallback={<FullPageSkeleton />}>
<ProfileView />
</Suspense>Route-Level Code SplittingSplit page bundles cleanly at router boundaries.
<Route
path="/analytics"
element={
<Suspense fallback={<PageSpinner />}>
<AnalyticsPage />
</Suspense>
}
/>Tips
- Nest multiple
<Suspense>boundaries across your component tree to reveal ready content immediately without waiting for slower siblings. - Pair
<Suspense>boundaries with<ErrorBoundary>components to handle both loading states and unexpected fetch failures gracefully.
Warnings
- Do not wrap every tiny component in its own boundary; excessive independent loading spinners create jarring visual layout
shifts. - Ensure all dynamic module imports supplied to
React.lazyexport their target UI components as thedefaultexport.
In Practice
Split a heavy analytics chart into an on-demand bundle and display a smooth skeleton fallback during resolution.
- Import heavy chart component dynamically using React.lazy.
- Declare fallback skeleton component mirroring chart dimensions.
- Wrap lazy component inside declarative Suspense boundary.
- Verify that page renders instantly while chart loads asynchronously.
const HeavyWidget = lazy(() => import('./Widget'));
function Skeleton() {
return (
<CardView>
<CardTitle>Loading Widget...</CardTitle>
</CardView>
);
}
export function Dashboard() {
return (
<CardView>
<HeaderView>Dashboard Overview</HeaderView>
<Suspense fallback={<Skeleton />}>
<HeavyWidget />
</Suspense>
</CardView>
);
}FAQ
Suspense lets components suspend execution while asynchronous operations (code splitting or data fetching) resolve, rendering a declarative fallback UI in the interim.
Yes. Modern libraries like TanStack Query, Relay, and the React 19 use() hook integrate directly with Suspense to pause rendering until promises resolve.
React.lazy splits large code bundles into smaller chunks downloaded only when components are first rendered, dramatically lowering initial page load times.