How you implemented code splitting in your project.
Split by route (lazy-load each page), by component (heavy/below-the-fold/modal components), and by vendor chunks. Use dynamic import() + React.lazy/Suspense, prefetch likely-next chunks, and measure with bundle analysis. Goal: small initial bundle, load the rest on demand.
Code splitting = breaking the bundle into chunks loaded on demand instead of one big upfront download. I'd describe where I split, how, and how I measured it.
Where to split
1. Route-based (the biggest win, do this first) Each route/page becomes its own chunk — users only download the code for the page they're on.
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
// wrap routes in <Suspense fallback={<RouteSkeleton/>}>2. Component-based Split heavy or rarely-used components even within a route:
- Below-the-fold sections, modals/dialogs, complex editors, charts.
- Heavy dependencies — a charting lib, a rich-text editor, a date picker — loaded only when that feature is used.
const ChartModal = lazy(() => import("./ChartModal"));3. Vendor / library splitting Bundler config (splitChunks in webpack, Vite's defaults) separates node_modules so vendor code is cached independently and an app change doesn't bust the vendor chunk.
4. Conditional / on-interaction Dynamically import() a feature only when triggered — e.g. the analytics dashboard code loads when the user opens that tab.
How
import()dynamic imports — the bundler automatically creates a chunk at each one.React.lazy+Suspensefor components, with a meaningful fallback (skeleton, not a bare spinner).- Route-level error boundaries so a chunk that fails to load shows a recoverable error (stale chunks after a deploy are a real failure mode — handle with a reload prompt).
Don't just split — prefetch
Lazy-loading adds a network round-trip when the user navigates. Mitigate:
- Prefetch on hover/intent — start loading the route chunk when the user hovers the link.
- Prefetch likely-next chunks during idle time.
- Frameworks (Next.js) prefetch in-viewport links automatically.
Measure
- Bundle analyzer (
webpack-bundle-analyzer,rollup-plugin-visualizer) — see what's in each chunk, find the bloat. - Track initial bundle size in CI with a budget; watch for regressions.
- Verify in the Network panel that chunks load when expected.
- Check it actually improved load metrics (LCP/TTI) — don't over-split into hundreds of tiny chunks (request overhead).
The framing
"I split route-first — each page lazy-loaded with React.lazy + Suspense — then split heavy components like modals, charts, and editors, and let the bundler separate vendor chunks. I added hover-prefetching so navigation still feels instant, wrapped routes in error boundaries for failed chunk loads, and tracked initial bundle size in CI with the bundle analyzer. The goal: a small initial download, everything else on demand, without over-splitting."
Follow-up questions
- •Why split by route first?
- •How do you avoid the navigation delay that lazy-loading introduces?
- •What happens when a lazy chunk fails to load (e.g. after a deploy)?
- •How can over-splitting hurt performance?
Common mistakes
- •Splitting without prefetching, making every navigation feel slow.
- •No error boundary for failed chunk loads — white screen after a deploy.
- •Over-splitting into hundreds of tiny chunks, adding request overhead.
- •Bare spinner fallbacks instead of layout-matched skeletons.
- •Never measuring — assuming splitting helped without checking bundle size or load metrics.
Performance considerations
- •Smaller initial bundle = faster TTI/LCP. But each chunk is a request — over-splitting trades parse time for round-trips. Prefetching hides the lazy-load latency. Vendor splitting improves cache hit rates across deploys.
Edge cases
- •Stale chunk references after a new deploy (chunk-load errors).
- •A lazy component needed immediately on first paint (don't split that).
- •SSR — lazy components need framework support to render on the server.
- •Shared code duplicated across chunks if split poorly.
Real-world examples
- •Route-based splitting in a React Router or Next.js app, each page its own chunk.
- •A heavy chart library loaded only when the analytics modal opens.