It's never one big mistake
When a client comes to us with “the app feels slow,” the bundle analyzer almost never reveals a single obvious culprit. It's a date-picker library imported for one form field, a charting library pulled in for a single dashboard widget, an icon package imported in full instead of per-icon, and a utility library where three functions are used out of the whole default import. Each decision was reasonable in isolation. Together, they add up to a bundle that loads slowly on a real network, on a real phone.
Where to actually look
Run a bundle analyzer before guessing — assumptions about what's “probably big” are wrong more often than not
Check for duplicate dependencies at different versions, which happens quietly in larger codebases with many contributors
Look for full-library imports where only a few functions or components are actually used
Confirm code-splitting is actually happening at route boundaries, not just configured and forgotten
Fixes that compound
Swapping a heavy date library for a lighter one, converting a full icon-package import to individual icon imports, and lazy-loading anything below the fold — modals, charts, rich editors — each look like small wins individually. In practice they compound: a bundle audit that finds five of these fixes at once often cuts initial load significantly without a single feature being removed.
The part that's easy to skip
The hardest part isn't finding the fixes, it's stopping the bundle from creeping back up next quarter. That means bundle-size budgets checked in CI, not a one-time cleanup that quietly erodes over the next six months of feature work. When we take over performance work on an existing React codebase, the CI check is usually the first thing we add — not the last.
