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.
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