Dealing with Heavy JavaScript: Techniques for Code Splitting
Large JavaScript bundles slow down page loads and block interactivity. Learn code splitting techniques to load only the JavaScript users need.
The average website ships 500KB-2MB of JavaScript. But on any given page, users only need a fraction of it. Code splitting breaks your JavaScript into smaller chunks that load on demand, dramatically improving performance.
Why Large Bundles Are a Problem
When you ship a single large JavaScript bundle:
- Download time — 1MB of JS takes 3+ seconds on slow 4G
- Parse time — the browser must parse all the JS before executing
- Execute time — all module-level code runs, even for unused features
- Memory — all code stays in memory
The result: high TBT, poor INP, and frozen pages during load.
The Real Cost
| Bundle Size | Download (4G) | Parse (mid phone) | Execute | Total |
|---|---|---|---|---|
| 100KB | 0.3s | 0.1s | 0.1s | 0.5s |
| 300KB | 0.9s | 0.3s | 0.3s | 1.5s |
| 500KB | 1.5s | 0.5s | 0.5s | 2.5s |
| 1MB | 3.0s | 1.0s | 1.0s | 5.0s |
Code Splitting Strategies
1. Route-Based Splitting
Load different JavaScript for different pages:
// React with React.lazy
const HomePage = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
2. Component-Based Splitting
Load heavy components only when needed:
// Heavy chart library only loads when user views analytics
const AnalyticsChart = lazy(() => import('./components/AnalyticsChart'));
function Dashboard() {
const [showAnalytics, setShowAnalytics] = useState(false);
return (
<div>
<button onClick={() => setShowAnalytics(true)}>View Analytics</button>
{showAnalytics && (
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
)}
</div>
);
}
3. Vendor Splitting
Separate your code from third-party libraries:
// webpack.config.js
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
},
},
},
}
This allows vendor code to be cached independently of your application code.
4. Dynamic Imports for Features
// Load PDF generator only when user clicks "Export"
async function exportToPDF(data) {
const { generatePDF } = await import('./utils/pdf-generator');
return generatePDF(data);
}
// Load syntax highlighter only for code blocks
if (document.querySelector('pre code')) {
const { highlightAll } = await import('./utils/syntax-highlight');
highlightAll();
}
Framework-Specific Implementation
Next.js
Next.js automatically code-splits by route. For component-level:
import dynamic from 'next/dynamic';
const HeavyEditor = dynamic(() => import('./Editor'), {
loading: () => <EditorSkeleton />,
ssr: false, // Don't render on server if not needed
});
Vite
Vite uses Rollup under the hood and splits chunks automatically:
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'chart-vendor': ['chart.js', 'd3'],
'editor-vendor': ['monaco-editor'],
},
},
},
},
};
Analyzing Your Bundles
webpack-bundle-analyzer
npm install webpack-bundle-analyzer
npx webpack --profile --json > stats.json
npx webpack-bundle-analyzer stats.json
source-map-explorer
npx source-map-explorer dist/app.*.js
Next.js Bundle Analyzer
ANALYZE=true npm run build
Prefetching Split Chunks
Code splitting can add latency when users navigate. Prefetch likely-needed chunks:
<!-- Prefetch the dashboard chunk while user is on homepage -->
<link rel="prefetch" href="/chunks/dashboard.js">
Or programmatically:
// Prefetch on hover
link.addEventListener('mouseenter', () => {
import('./pages/Dashboard');
});
Results to Expect
| Metric | Before Splitting | After Splitting |
|---|---|---|
| Initial JS | 800KB | 150KB |
| LCP | 3.5s | 1.8s |
| TBT | 1200ms | 200ms |
| Lighthouse | 45 | 85 |
Monitor Your Bundle Performance
Bundle sizes creep up as features are added. BadPageSpeed tracks your page performance so you catch JavaScript bloat early.
Monitor your JavaScript performance →
Related Reading
Ready to stop wasting ad spend?
Track your landing page performance, monitor Core Web Vitals, and calculate exactly how much slow pages cost you.
Start Free — No Credit Card