QuestionsWeb Performance

Code-splitting and lazy-loading routes

Code SplittingMediumWeb Performance

Your app ships one 800KB JS bundle and time-to-interactive is poor. How does code-splitting help, and where do you split?

What it tests

Understanding that you should ship only the code a given screen needs, and the mechanics of doing so.

Approach & answer

A single bundle forces every visitor to download, parse, and execute ALL your code before anything is interactive — even code for routes they'll never visit. Parsing and executing JS is CPU work that blocks the main thread, so a big bundle hurts time-to-interactive and INP especially on cheap phones. Code-splitting breaks the bundle into chunks loaded on demand, so the initial download is just what the first screen needs. The natural seams: (1) Per route — the single highest-leverage split. Lazy-load each route's component so visiting /settings fetches settings code only when navigated to. (2) Below-the-fold or interaction-gated components — a heavy chart, a rich editor, a modal — load when they're about to be shown, not on initial load. (3) Large third-party libraries used in one place — dynamically import the date-picker or the markdown renderer at the point of use. The mechanism is the dynamic `import()` expression, which returns a promise and tells the bundler to emit a separate chunk. Frameworks wrap this: React.lazy + Suspense render a fallback while the chunk loads. Two caveats: don't over-split (each chunk is a request and a potential waterfall — splitting a 2KB component is pure overhead), and prefetch likely-next chunks during idle time (e.g., <link rel=prefetch> or on hover) so the lazy load doesn't cost a visible delay when the user actually navigates. The goal is a small, fast initial bundle plus just-in-time loading of the rest.

Use this technique when

Cutting initial bundle size; improving TTI/INP on a JS-heavy app.

Code

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

// Each route becomes its own chunk, fetched only when visited
const Dashboard = lazy(() => import('./routes/Dashboard'));
const Settings  = lazy(() => import('./routes/Settings'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// Interaction-gated: load a heavy editor only when opened
async function openEditor() {
  const { RichEditor } = await import('./RichEditor'); // separate chunk
  mount(RichEditor);
}

References