Tree shaking and splitting only help if you know what's actually in your bundle. A bundle analyzer turns the build into a picture — a treemap where every box is a module sized by its weight — so the bloat you never suspected becomes impossible to miss. This is how a 142 KB bundle becomes 61 KB without removing a single feature.
The tool
Seeing inside the build
Your bundler already emits a stats file describing every module it included and how big each is. A bundle analyzer reads that and draws an interactive treemap. Because the step is bundler-specific, each has its own: webpack-bundle-analyzer, Vite's rollup-plugin-visualizer, and so on — same idea everywhere.
It exists to answer the questions you ask during a performance audit:
Why is this bundle 2 MB — what's actually in it?
How big is my code vs. the third-party deps?
Did tree shaking work, or did a whole library sneak in?
Is a dependency duplicated across chunks?
See it
Explore a real bundle
Here's a tiny To-Do app that imported moment (for a date) and lodash (for one sum). The treemap shows the truth: your code is a sliver, and the dependencies dwarf it. Click boxes to inspect them, then hit Apply fixes and watch the bloat collapse:
InteractiveA bundle treemap (box size = weight)
Total (gzip):142 KB
moment locales · moment.js
Every language locale moment ships by default — you only need English. IgnorePlugin removes all of this.
moment ships every locale; lodash came in whole for one function. Each box is sized by its real cost — the fixes shrink them dramatically.
Now build one yourself. Every weight below is a real published min+gzip size from npm — swap each heavy habit for its lighter twin and watch the download and phone-parse costs collapse:
InteractiveBuild your own bundle — real package weights
your bundle247.3 KB min+gzip
react
moment
lodash
axios
chart.js
Total (min+gzip)
247.3 KB
Download · fast 4G
495 ms
Parse · mid-range phone
~643 ms
Package sizes are real published min+gzip weights. Note what the swaps have in common: same features, smaller habits — none of these required redesigning the app.
moment → dayjs, lodash → named lodash-es imports, axios → fetch, chart.js → uPlot, uuid → crypto.randomUUID(). Same features, −74% of the bytes.
Read the right number
Stat vs parsed vs gzipped
Analyzers report three sizes, and confusing them leads to wrong conclusions:
Stat — raw source size, before minification. Biggest, least meaningful.
Parsed — after minification; what the browser actually parses & executes.
Gzipped — after compression; the real download cost over the wire.
Tip
For download time, look at gzipped. For main-thread/parse cost, look at parsed. Toggle the formats in the demo — the same module can look very different depending on which you read.
The usual offenders
The two classic culprits
moment.js shipping every locale
moment bundles all its language locales by default — often more weight than the library itself, for translations you'll never use. Strip them (or switch to a lighter date lib):
drop moment locales with Webpack
constwebpack=require("webpack");plugins:[// Don't bundle any moment locale filesnewwebpack.IgnorePlugin({resourceRegExp:/./locale$/,contextRegExp:/moment$/}),];
lodash imported whole
Plain lodash isn't tree-shakable, so import _ from "lodash" drags in everything for one function. Import the single function, or use the ESM build:
import only what you use
// ❌ pulls in all of lodash (~24 KB gzipped)import_from"lodash";_.sum(nums);// ✓ just the one function (~0.2 KB)importsumfrom"lodash/sum";// …or the tree-shakable ESM build:import{sum}from"lodash-es";
Key idea
These two one-line changes took the demo bundle from 142 KB → 61 KB gzipped — moment 74→18, lodash 24→0.2 — with the app behaving identically. That's the whole point: the analyzer tells you where to look.
Q1Multiple choice
In the analyzer, a module is 12 KB gzipped but 180 KB parsed. What does each number mean, and which is the bigger concern?
Q2Multiple choice
The treemap shows two copies of date-fns at different versions inside your bundle. What's likely happening, and the fix?
Q3Multiple choice
A teammate runs the analyzer, sees a big bundle, and concludes 'we need a faster bundler.' Better read of the treemap?
Q4Sort each scenario
Match each treemap finding to its targeted fix.
moment.js bloated by every language locale
All of lodash pulled in for one function
Two versions of react-query bundled together
A 300 KB admin-only rich-text editor in the main chunk
A charting lib imported via import *
Key takeaways
→A bundle analyzer visualizes the build as a treemap — box size = module weight.
→It answers the audit questions: why is it big, my code vs deps, did tree-shaking work, anything duplicated.
→Read gzipped for download cost, parsed for parse/CPU cost.
→Classic culprits: moment locales and whole-lodash imports.
→Targeted fixes from one look can halve a bundle with zero feature loss.