All topics
Performanceintermediate

Code Splitting and Lazy Loading Modules

Breaking a JavaScript bundle into smaller chunks loaded on demand, using dynamic import() to reduce the initial page load's JavaScript payload.

Code splitting is the practice of breaking a JavaScript application's bundle into multiple smaller chunks that are loaded on demand, rather than shipping one enormous bundle containing every possible feature's code upfront, regardless of whether the current user actually needs most of it on initial page load. Since unnecessary JavaScript directly delays a page becoming interactive, this is one of the most impactful, practical frontend performance techniques, and it's built on a specific native language feature: dynamic import().

Code splitting is like a restaurant that doesn't print and hand every guest its entire encyclopedic menu including seasonal items, off-menu specials, and the dessert list all at once — it hands you the core menu immediately so you can start ordering right away, and only brings out the dessert menu (a separate chunk) later, specifically once you actually ask for it.

Key Concepts

1
Unlike static import statements, which must appear at the top level of a module and are resolved at parse/build time, the dynamic import(modulePath) function can be called anywhere in your code, at any time, conditionally — and it returns a Promise that resolves with the imported module once it's actually fetched and evaluated, exactly like an async network request, because that's essentially what it is: a separate JavaScript file being fetched over the network on demand, rather than being bundled into the main initial payload.
importimport(modulePath)
2
Bundlers (Webpack, Vite, Rollup, and others) recognize dynamic import() calls specifically as code-splitting boundaries: any module reached only through a dynamic import gets extracted into its own separate output chunk file, which the bundler configures the browser to fetch only when that import() call actually executes at runtime, rather than being included in the initial bundle download at all. This is exactly the mechanism behind common patterns like React's React.lazy() for component-level code splitting (only loading a route's or modal's code when the user actually navigates to or opens it) and route-based splitting in frameworks' routers (only loading a given page's JavaScript when the user actually navigates there).
import()React.lazy()
3
The tradeoff is a genuine latency cost at the moment a lazily-loaded chunk is actually needed — there's a network round-trip (even if often fast, especially with HTTP/2 multiplexing and preloading hints) before that chunk's code becomes available, which is why code splitting is usually paired with a loading indicator/fallback UI for the brief gap, and sometimes combined with 'prefetching' a chunk slightly ahead of when it's actually needed (like on hover over a link, anticipating a likely-imminent navigation) to hide that latency before the user actually triggers the interaction requiring it.