All topics
Routingintermediate

Code Splitting Routes with Lazy Loading

Learn how to split each route's code into a separate bundle loaded on demand, reducing the app's initial load size.

Code splitting routes means bundling each route's component (and its dependencies) into its own separate JavaScript chunk that's only downloaded when the user actually navigates to that route, rather than including every possible page's code in one large initial bundle. This directly reduces the amount of JavaScript the browser must download, parse, and execute before the user sees the first page.

Route-based code splitting is like a streaming service that only downloads the episode you're actually about to watch instead of the entire series' video files upfront — you get a brief loading spinner the first time you open a new episode (route), but you never pay the bandwidth cost for episodes you never watch.

Key Concepts

1
React.lazy(() => import('./SomePage')) combined with a <Suspense> boundary is the standard mechanism: React.lazy returns a component that, on first render, triggers the dynamic import() for that module and suspends rendering (showing the nearest <Suspense fallback>) until the chunk finishes downloading and the component becomes available. Once loaded, subsequent renders of that lazy component use the already-fetched module without re-fetching.
React.lazy(() => import('./SomePage'))<Suspense>React.lazyimport()<Suspense fallback>
2
Applied per-route, this means a user visiting only the home page never downloads the code for a rarely-visited admin settings page, and large third-party dependencies used by only one route (like a heavy charting library) don't bloat the bundle for users who never visit that route at all — bundlers like webpack or Vite automatically split each dynamically imported module into its own chunk file.
3
Interviewers ask candidates to identify where Suspense boundaries should be placed relative to lazily loaded routes (commonly once near the router's root, or per-route for more granular loading states) and to explain the tradeoff: smaller initial bundle size at the cost of a brief loading state the first time each route's chunk is fetched.