Tags: web-dev concept

Bundle Analysis

Date: 2026-08-17


Opening the payload to see what’s actually in it. It’s never what you expect — the largest item is routinely a dependency nobody chose directly, and the fix is usually deletion rather than optimisation.


Bundle analysis is inspecting the built JavaScript and CSS output to see which modules make up its size, usually as a treemap.

What the treemap shows

Every bundler has an analyser that renders the output as nested rectangles sized by bytes. The shape is always the same story:

main.js — 412 KB gzipped

┌────────────────────────────────────────────────────────────┐
│ node_modules                                     368 KB    │
│ ┌──────────────────────┬──────────────────┬──────────────┐ │
│ │ moment + locales     │ lodash           │ chart lib    │ │
│ │      118 KB          │     71 KB        │   64 KB      │ │
│ │ ← 96 KB is locales   │ ← whole library  │ ← used on    │ │
│ │   you never use      │   for 3 helpers  │   ONE admin  │ │
│ │                      │                  │   page       │ │
│ ├──────────────────────┴────┬─────────────┴──────────────┤ │
│ │ polyfills          48 KB  │ 3 date libraries      41 KB│ │
│ │ ← for browsers you        │ ← moment, date-fns, dayjs  │ │
│ │   dropped support for     │   all pulled in by deps    │ │
│ └───────────────────────────┴────────────────────────────┘ │
├────────────────────────────────────────────────────────────┤
│ your application code                              44 KB   │
└────────────────────────────────────────────────────────────┘

Your own code is 11% of the bundle. That ratio is typical, and it’s the reason optimising your components is rarely where the win is.

The five things you’ll find

Almost every first analysis turns up some of these:

  • Locale and timezone data. Date libraries ship every locale by default. Often the single largest item, and usually removable with a bundler plugin or a smaller library
  • A whole library for one function. import _ from 'lodash' pulls everything; import debounce from 'lodash/debounce' pulls one function. The named-import version of the same thing often still pulls everything, depending on the package’s module format — Module Bundling
  • Duplicate dependencies. Two versions of the same package because two of your dependencies pinned different ranges. Costs the full size twice
  • Polyfills for browsers you don’t support. A stale browserslist target is one line to fix and often 40–60 KB
  • A heavy component on the wrong route. A charting or rich-text library in the main bundle because one admin page imports it — the clearest Code Splitting signal there is

Reading it correctly

Compare compressed sizes, not raw. Raw bytes overstate text-heavy code, which compresses well, and understate already-compact code. Most analysers show both — use the gzip or Brotli column — Compression.

But size isn’t the cost. Bytes are the cheap part; parse, compile and execute time is what delays interactivity, and it scales with the amount of code, not its compressed size. A 40 KB library that runs on load costs more than a 90 KB one that’s never called — JavaScript Execution Cost.

Check what’s in the initial bundle specifically. Total bundle size across all chunks is close to meaningless. What matters is what a first-time visitor downloads and executes before the page is usable.

✗  "our bundle is 800 KB"
✓  "the product page's initial JS is 180 KB compressed,
    of which 112 KB executes before first interaction"

The fixes, in order of return

1  DELETE            an unused dependency, an abandoned feature,
                     a polyfill for a browser you dropped
                     ← free. always check this first

2  SPLIT             move it off the initial route
                     ← Code Splitting. cheap, no behaviour change

3  SUBSTITUTE        a smaller library for the same job
                     ← real work, real risk

4  REMOVE THE NEED   do it server-side, or use a platform feature
                     ← the biggest wins, the most work

5  OPTIMISE          minify harder, tune the bundler
                     ← usually a few percent. do this last

Most teams start at 5 and never reach 1, because deleting things requires knowing whether they’re used and optimisation doesn’t.

Making it a habit rather than an exercise

A one-off analysis produces a good week and then decay — bundles grow by one reasonable addition at a time.

  • Print the bundle size on every pull request, with the delta against the base branch. A +40 KB diff in the review is a conversation; the same 40 KB discovered in a quarterly audit is archaeology — Performance Regression Testing
  • Fail the build past a threshold rather than warning — Performance Budgets
  • Watch the dependency count as well as the size. Every addition brings transitive dependencies and a maintenance obligation — Dependency Management, Supply Chain Risk
  • Re-check after major dependency upgrades, which is when duplicate versions appear

Where it interacts