How to Reduce JavaScript Bundle Size
Large JavaScript bundles are the #1 cause of slow TBT and high INP. Learn how to audit your bundle, remove unused code, and split it for faster load times.
JavaScript is expensive in a way that other assets aren’t. A 500KB image costs bandwidth but only occupies one thread. A 500KB JavaScript file costs bandwidth and must be parsed and executed on the main thread — potentially blocking it for seconds on a mid-range mobile device.
Large JavaScript bundles are the primary driver of high Total Blocking Time (TBT) and poor INP scores. Reducing bundle size is the most effective way to improve responsiveness.
Audit Your Current Bundle
Before optimizing, understand what’s in your bundle.
For webpack:
npm install --save-dev webpack-bundle-analyzer
Add to webpack.config.js:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = { plugins: [new BundleAnalyzerPlugin()] };
For Vite:
npm install --save-dev rollup-plugin-visualizer
Bundlephobia — paste any npm package name to see its minified+gzipped size and tree-shakability.
Look for:
- Packages that are much larger than expected
- Duplicate dependencies (multiple versions of lodash, etc.)
- Packages where you’re only using a small portion of their functionality
- Polyfills that aren’t needed for your browser targets
Remove Unused Code (Tree Shaking)
Modern bundlers (webpack, Rollup, esbuild, Vite) support tree shaking — they remove exports that aren’t imported anywhere. But it only works with ES modules (import/export), not CommonJS (require).
Make sure you’re using named imports rather than whole packages:
// Bad — imports the entire lodash library (~70KB)
import _ from 'lodash';
const sorted = _.sortBy(data, 'name');
// Good — imports only what you need (~1KB)
import sortBy from 'lodash/sortBy';
const sorted = sortBy(data, 'name');
For date libraries, consider date-fns over moment.js — it’s tree-shakable and much lighter.
Replace Heavy Dependencies
Some popular packages have lighter alternatives with similar APIs:
| Heavy (minified, uncompressed) | Lightweight alternative |
|---|---|
moment.js (72KB) | date-fns or dayjs (2–5KB) |
lodash (full, 70KB) | Native JS methods or lodash-es |
axios (13KB) | fetch API (built-in) |
jQuery (87KB) | Vanilla JS (usually) |
chart.js (187KB) | lightweight-charts or D3 subset |
Always check if the browser API now covers what the library does. Most modern JS apps don’t need jQuery or Lodash at all.
Code Split by Route
If you’re using a framework like React, Vue, or Svelte, don’t load all your code on every page. Split your bundle by route so users only download what they need.
React with React Router:
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<Route path="/dashboard" element={<Dashboard />} />
</Suspense>
);
}
Vite handles code splitting automatically when you use dynamic import().
Use Dynamic Imports for Non-Critical Features
Components and features that aren’t needed on initial load should be loaded on demand:
// Load a heavy charting library only when the user navigates to the analytics tab
const loadChart = async () => {
const { Chart } = await import('chart.js');
return new Chart(ctx, config);
};
tabButton.addEventListener('click', loadChart);
Set Correct Browser Targets
If you’re transpiling ES2015+ to ES5 for older browsers, you may be generating significantly more code. Update your Babel or TypeScript target to the browsers you actually support:
last 2 Chrome versions
last 2 Firefox versions
last 2 Safari versions
This can reduce bundle size by 10–20% for codebases that use modern JS features heavily.
Enable Compression
Make sure your server is serving JavaScript with gzip or Brotli compression. Brotli typically achieves 15–25% better compression than gzip.
Most CDNs and hosting platforms do this automatically, but verify it in your request waterfall — look for content-encoding: br or content-encoding: gzip headers.
Measuring the Impact
Run a before/after test on debugutils.dev to see the impact of your changes on TBT, LCP, and overall performance score. The waterfall view shows each JavaScript file’s transfer size, so you can confirm compression and see download times.