| name | code-splitting |
| description | Implement code splitting to reduce initial bundle size and improve web application load time. Outputs splitting strategy, lazy loading patterns, preloading rules, and performance measurement approach. |
| argument-hint | ["framework","current bundle size","performance targets","route structure"] |
| allowed-tools | Read, Write, Bash |
Code Splitting
Code splitting breaks a large JavaScript bundle into smaller chunks loaded on demand. Instead of loading everything at startup, the browser loads only what's needed for the current view. The result is faster initial load, better Time to Interactive (TTI), and lower data usage.
Process
- Measure first. Analyse the current bundle with webpack-bundle-analyzer or similar. Find the biggest chunks.
- Split at route boundaries. Each route loaded lazily is the highest-impact split.
- Split large third-party libraries. Chart libraries, date pickers, editors — load only when used.
- Add preloading for likely next routes.
<link rel="prefetch"> or React.lazy with prefetch.
- Set performance budgets. Max bundle size per chunk. Fail CI if budgets are exceeded.
- Measure improvement. Before/after Lighthouse scores, Core Web Vitals.
React Route-Based Splitting
import React, { Suspense, lazy } from "react";
import { Routes, Route } from "react-router-dom";
import { LoadingSpinner } from "./components/LoadingSpinner";
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Orders = lazy(() => import("./pages/Orders"));
const Analytics = lazy(() => import("./pages/Analytics"));
const Settings = lazy(() => import("./pages/Settings"));
const RichTextEditor = lazy(() =>
import("./components/RichTextEditor").then(m => ({ default: m.RichTextEditor }))
);
export function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/orders/*" element={<Orders />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
const preload = () => {
if (to === "/analytics") import("./pages/Analytics");
if (to === "/orders") import("./pages/Orders");
};
return (
<Link to={to} onMouseEnter={preload} onFocus={preload}>
{children}
</Link>
);
}
Vite / Webpack Bundle Analysis
npm install -D rollup-plugin-visualizer
vite build
npm install -D webpack-bundle-analyzer
webpack --analyze
Dynamic Imports for Heavy Libraries
import { Chart } from "chart.js";
import "chart.js/auto";
async function renderChart(canvas: HTMLCanvasElement, data: ChartData) {
const { Chart } = await import("chart.js/auto");
return new Chart(canvas, { type: "bar", data });
}
function useChartJs() {
const [ChartJs, setChartJs] = React.useState<typeof import("chart.js") | null>(null);
React.useEffect(() => {
import("chart.js/auto").then(setChartJs);
}, []);
return ChartJs;
}
Performance Budget (CI Gate)
module.exports = {
files: [
{ path: "dist/assets/index-*.js", maxSize: "150kb" },
{ path: "dist/assets/vendor-*.js", maxSize: "200kb" },
{ path: "dist/assets/Dashboard-*.js", maxSize: "80kb" },
{ path: "dist/assets/Analytics-*.js", maxSize: "120kb" },
],
};
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Fix |
|---|
| Splitting every component | Too many small chunks; HTTP overhead | Split at route or feature boundaries |
| No Suspense boundary | Lazy component errors crash the app | Wrap every lazy component in Suspense |
| Loading without preloading | Users wait on navigation | Preload on hover/focus for predictable next routes |
| Splitting without measuring | Random splits may not help | Analyse bundle first; split biggest chunks |
| No loading states | Layout shift when chunk loads | Skeleton screens or spinners for lazy sections |
10 Rules
- Measure before splitting — bundle analyser reveals actual bottlenecks.
- Route-based splitting is the highest-impact first step.
- Heavy third-party libraries (charts, editors, date pickers) are split separately.
- Every
React.lazy() has a <Suspense> boundary with a loading fallback.
- Preload likely next routes on hover — eliminates perceived loading delay.
- Set performance budgets and enforce them in CI — chunks grow without gates.
- Prefer named exports from split chunks — tree-shaking works better.
- Prefetch on network idle for routes users haven't visited but likely will.
- Test on throttled connections — 3G simulation reveals real-world impact.
- Core Web Vitals (LCP, TTI) are the outcome metrics — bundle size is a proxy.