In a single-page app, route-based code splitting ships each route's JavaScript as a separate chunk fetched on navigation. What does that change about the first page load, and what does it not change about the total JavaScript a user who eventually visits every route downloads?
answer
- moves bytes, does not delete them
- first screen versus whole session
- parse and execute, not just transfer
- navigation now pays a round trip
- total across all routes is slightly larger
basics
~20 sRoute-based splitting shrinks the first download to the landing route's code, so the app becomes interactive sooner. It does not reduce total JavaScript: a user who visits every route still fetches all of it, just later and in more requests.
solid answer
~50 sRoute splitting is a **scheduling** change, not a size reduction. Instead of one bundle containing every screen, the entry chunk carries only the shell plus the landing route, and each other route is fetched when the user navigates to it. The win is on first load: fewer bytes to download, parse, and execute before the page is usable, which is what most first-visit metrics actually measure. What it does not do is make the app smaller. Someone who tours every route downloads roughly the same total code — slightly more, in fact, because of per-chunk overhead and worse compression on many small files — and each navigation now costs a network round trip that the monolithic bundle had already paid for. So it trades total bytes and navigation latency for a much cheaper first paint, which is almost always the right trade because most sessions touch only one or two routes.
code
javascript · 16 linesconst routes = {
'/': () => import('./routes/home.js'),
'/reports': () => import('./routes/reports.js'),
'/admin': () => import('./routes/admin.js'),
};
async function navigate(path) {
const loader = routes[path];
if (!loader) return;
const mod = await loader();
mod.render(document.querySelector('#app'));
}
// Cover the navigation round trip: warm the likely next route.
document.querySelector('a[href="/reports"]')
?.addEventListener('pointerenter', () => routes['/reports'](), { once: true });go deeper
Be able to say plainly that splitting delays code rather than deleting it, and that the benefit lands on the first load. Know that the router imports each route on demand instead of up front.
Explain the three costs that shrink — transfer, parse/compile, execute — and the new cost introduced at navigation time. Be ready to describe prefetching the likely next route to hide that round trip.
Show judgment about which routes earn a boundary: weigh unique code size against the share of sessions that reach the route, and point out that splitting a first-paint dependency moves bytes later without removing them from the critical path.
Frame it as optimizing the distribution of real sessions rather than a worst-case tour, and connect chunk boundaries to cache lifetime across deploys — per-route hashing means a change to one screen no longer invalidates everyone's cached bundle.
## What route-based splitting is Without splitting, a build produces one JavaScript file containing every screen, dialog, and helper the application can ever show. The browser must download that file, parse it, and execute it before the app's own code can run — even if the visitor only ever looks at the login page. Route-based splitting means the router does not statically import the modules for each screen. Instead it holds a function per route that dynamically imports the module, and the bundler turns each of those import boundaries into a separate output file (a *chunk*). Only the entry chunk — application shell, router, shared framework code, plus whatever the landing route needs — is fetched up front. ```js const routes = { '/': () => import('./routes/home.js'), '/reports': () => import('./routes/reports.js'), '/admin': () => import('./routes/admin.js'), }; async function navigate(path) { const mod = await routes[path](); mod.render(document.querySelector('#app')); } ``` ## What the first load gains Three costs shrink together, and they are not the same cost: 1. **Transfer** — fewer compressed bytes over the network, which matters most on slow or metered connections. 2. **Parse and compile** — engines must parse the JavaScript they receive; this is roughly proportional to uncompressed size and is felt hardest on low-end mobile CPUs. 3. **Execution** — module bodies for screens the user never opens no longer run at startup. The second and third are why splitting helps more than the byte count alone suggests. A 200 KB reduction in transfer is also a few hundred milliseconds of main-thread work removed from the critical startup window on a mid-range phone. ## What it does not change **Total code over a full session.** Splitting moves bytes; it does not delete them. A user who visits home, reports, and admin fetches all three chunks. If anything they fetch slightly *more* than the single bundle would have been: each chunk carries a little module bookkeeping, and compression algorithms find fewer repeated patterns in small files than in one large one, so the sum of compressed chunk sizes usually exceeds the compressed monolith. **Dead code.** Splitting is orthogonal to removing unused exports. A module that is 90% unused is still 90% unused inside its own chunk. **Navigation cost.** The monolithic bundle had already paid for every route's code, so route changes were instant. With splitting, the first visit to a route waits on a network request. On a good connection that is tens of milliseconds; on a bad one it is a visible stall, and it lands during an interaction, where users are least tolerant of delay. Mitigations are prefetching the likely-next route during idle time and keeping route chunks small enough that a fetch is cheap. **Whether the split helps at all.** If a module is needed to render the first screen, moving it into a lazy chunk does not remove its bytes from the critical path — it just makes them arrive one round trip later. Splitting only pays when the split-off code is genuinely not needed yet. ## Why the trade is usually right Session data on most products is heavily skewed: a large share of visits touch one or two routes and then leave. Optimizing for the total bytes of an exhaustive tour optimizes for a user who does not exist. Optimizing for the first screen optimizes for nearly everyone, including first-time visitors who have nothing cached and are the most likely to bounce. There is a caching angle too. With one bundle, changing any line invalidates the whole file for every returning user. With per-route chunks and content-hashed filenames, editing the admin screen invalidates only the admin chunk; everything else stays warm in the HTTP cache. ## What good answers mention - Splitting is a *deferral*, not a diet: name total-bytes-unchanged explicitly. - The parse/execute saving, not just the transfer saving. - The new cost it introduces — a round trip at navigation time — and how you cover it (prefetch on intent or during idle). - Route boundaries are the natural first split because they align with what the user is not looking at yet, and because there are only a handful of them, which keeps chunk count sane.
- If total bytes go up, why is route splitting still considered a performance win?Because the metric that matters is bytes on the critical path of a real session, not bytes summed over an exhaustive tour of the app. Most sessions visit one or two routes, so the extra total is paid by almost nobody, while every visitor benefits from a smaller, faster-parsing first load. The overhead per chunk is also small relative to a route's own code.
- A team splits a component that renders in the landing route's first paint. What happens?Nothing improves and something gets worse. The bytes are still required before first paint, so they stay on the critical path — but now they are discovered only after the parent chunk executes, adding a network round trip. You also risk a visible placeholder or a layout shift where there was none. Split code the first screen does not need.
- How would you decide which routes are worth splitting first?Look at two numbers per route: how much code it uniquely owns, and what share of sessions reach it. The best candidates are large and rarely visited — admin panels, settings, editors, onboarding flows, anything behind a permission check. A small route visited by everyone is not worth a chunk boundary.
saying these in an interview costs you the question
- Claims splitting reduces the app's total JavaScript size
- Thinks splitting removes unused code, confusing it with tree shaking
- Splits code the first screen needs and expects a win
- Forgets that each navigation now costs a network request
- Assumes more chunks is always better