Bundle size: why tree shaking fails

React chapter 146 · Web Perf · ch.7 · tree shaking, ESM vs CommonJS, namespaces, duplicate libraries · source: Nadia Makarevich, Web Performance Fundamentals, Chapter 7 (bundle size, tree shaking, non-tree-shakable libraries, transitive dependencies)

The problem

The app uses one icon, one MUI component, two lodash helpers and formats one date — yet the vendor chunk is megabytes. A namespace object (Ui = { Icons }), a CommonJS library and three date libraries each defeat tree shaking in a different way.

The pattern that fixes it

Open the bundle analyzer, click the biggest block, find the import that pulled it in. Import exactly what you use from ES-module entry points, drop non-tree-shakable libraries or use their per-function paths, keep one library per job, and lazy-load heavy page-only code.

In plain words

Your bundler copies only the code you actually use into the files users download — “tree shaking”. But it can only prove what is unused when imports are simple and static. A few common habits make it give up and include the whole library.

Packing for a trip by reading your itinerary. If the itinerary says “I might need anything from the garage”, the whole garage goes in the suitcase.

Terms

tree shaking
Removing exports nothing imports. Works on static import/export (ES modules).
ESM vs CommonJS
export const x is analysable at build time. module.exports = {…} is just an object at runtime — usually included whole. Check with npx is-esm <pkg>.
bundle analyzer
A treemap of what is in each chunk: rollup-plugin-visualizer (Vite), webpack-bundle-analyzer, @next/bundle-analyzer.

Going deeper

Bytes cost twice

A big bundle costs download time (bad on slow networks, chapter 143) and then parse + compile + evaluate time on the main thread (bad on slow CPUs, it delays LCP in CSR and blocks hydration in SSR). Gzip/Brotli shrink the download, not the parse: the phone still evaluates every minified byte.

Why the namespace object breaks it

import * as Icons alone is fine — the bundler sees Icons.Star and keeps only Star. But export const Ui = { Icons } turns the namespace into a runtime value that could be read with any key (Ui.Icons[name]). The bundler must keep every export. The same happens with re-export barrels that have side effects, or "sideEffects" missing from a library’s package.json.

Transitive dependencies and duplicates

A package you never imported can be in the bundle because a dependency of a dependency needs it — sometimes in a second version. npm why <pkg> shows the chain. Duplicates (moment + luxon + date-fns, two copies of a library at different versions) are common in big multi-team apps; dedupe with one library per job and package-manager overrides/resolutions.

Guard it in CI

Bundle size regressions arrive one innocent import at a time. Add a size budget (size-limit, bundlewatch, or the framework’s build output) to CI so the PR that adds 400 KB fails with a clear diff.

Keep learning

More courses

  • Jobs, linked to lessons — Browse jobs where every requirement links to the chapter that teaches it. See how ready you are, how long the gap takes to close, and which listings look stale.
  • Match a job post to courses — Paste a job post. Each skill is matched to a track here and put in learning order, foundations first.
  • Database in depth — How a query becomes work. Indexes, isolation, locks, and the plans that explain why a page is slow — not an ORM tour.
  • System Design — Eight interview case studies, from a flash sale and Tatkal to a live final and a chat model, plus JWT in separate chapters.
  • Microservices — Microservice patterns one diagram at a time: how to split a monolith, how services talk, and how data stays consistent without distributed transactions.
  • Kafka — A beginner-to-senior path through event logs, reliable delivery, production operations, and disaster recovery.
  • Frontend system design — How a UI is sliced when it is a product, not a page: ownership of state, cache, routes, and what happens when the network lies.
  • CSS in depth — See the box, then the page. Visual chapters from the box model through Flexbox surprises, Grid, selectors, and units, then backgrounds, shapes, and visual effects, each with a playground.
  • Accessibility — HTML labs first, then a full pass of WAI-ARIA 1.3: naming, every state, widget and structure roles, live regions, ariaNotify, and relationships.
  • Docker — What an image is, why layers cache, and how containers find each other on a network.
  • Redis — Fifteen Redis patterns, one picture each: cache, session, lock, counter, rate limiter, queue, and when Redis is the wrong tool.