How do you split a large bundle so a rarely-used route (say, an admin dashboard) doesn't bloat the initial load, and how does Suspense fit in?
Connecting bundle size to lazy loading and the declarative loading-state model.
Everything imported at the top level ships in the initial bundle, even code the user may never hit. React.lazy defers a component's code to a separate chunk that the bundler (Webpack/Vite) emits and only fetches when the component first renders — `React.lazy(() => import('./AdminDashboard'))`. Because that import is async, React needs something to show while the chunk downloads: <Suspense fallback={...}> wraps the lazy component and declaratively renders the fallback (a spinner/skeleton) until it resolves. This is the same Suspense mechanism that data-fetching libraries and React Server Components hook into — a component 'suspends' by throwing a promise, and the nearest Suspense boundary catches it and shows the fallback until it settles. Place boundaries thoughtfully: too high and one slow chunk blanks a large region; too granular and you get spinner soup. Common wins: split by route, and lazy-load heavy below-the-fold or modal-only components. Pair with an error boundary since a chunk fetch can fail (network), and consider prefetching the chunk on hover/intent so the fallback rarely shows. Note React.lazy needs a default export (or wrap a named export).
A route/feature's code shouldn't load until needed → React.lazy + a Suspense boundary with a fallback.
const AdminDashboard = React.lazy(() => import('./AdminDashboard'));
function App() {
return (
<React.Suspense fallback={<Spinner />}>
<Routes>
<Route path="/admin" element={<AdminDashboard />} /> {/* chunk fetched on first visit */}
</Routes>
</React.Suspense>
);
}