Frontend Bundle Size Optimization

Bundle size is one of the most-tracked frontend metrics. Every kilobyte costs render time, especially on mobile. Senior frontend interviews probe whether you have done bundle analysis and applied targeted optimizations — not just “I use code splitting.”

Setting a budget

For a typical web app:

  • Initial JS: <200KB compressed
  • Initial CSS: <50KB compressed
  • Vendor chunk: <150KB compressed
  • Each route chunk: <100KB compressed

For static / content sites: <100KB total. For complex apps (Figma, Linear): higher budget acceptable.

Bundle analysis tools

  • vite-bundle-visualizer (Vite)
  • rollup-plugin-visualizer
  • webpack-bundle-analyzer
  • source-map-explorer (post-build)

Always run on production builds. Look for: unexpected large libraries, duplicates, dependencies you forgot you imported.

Dependency audit

The biggest wins come from cutting heavy dependencies:

  • moment.js (200+KB): replace with date-fns (modular) or Day.js (2KB)
  • lodash (full): import per-function (lodash/debounce) or replace with native
  • chart.js / D3 full: bundle only the parts you use
  • icon libraries: import individual icons, not the whole library

Tree shaking effectiveness

Tree shaking only works if:

  • The library is ESM (or has ESM build)
  • The library is side-effect-free (or its package.json declares so)
  • You import named exports, not entire modules

Bad: import _ from 'lodash' imports everything

Good: import { debounce } from 'lodash' still might bundle everything if lodash is not properly tree-shaken

Best: import debounce from 'lodash/debounce' imports only debounce

Code splitting

Beyond route-based:

  • Heavy components on demand (charts, editors, players)
  • Separate vendor and app bundles
  • Split rarely-used dialogs and modals

Polyfills

Modern apps target modern browsers. Don’t ship polyfills for IE if you do not support IE.

Use browserslist config; tools like Babel and PostCSS use it to determine what to polyfill.

Image impact on bundles

Small images sometimes get inlined as data URIs. Disable for anything >1KB. Inline blocks tree shaking and bloats main bundle.

CSS bundle size

  • PurgeCSS (or Tailwind’s built-in) removes unused classes
  • CSS Modules scope rules to components — used CSS only
  • Avoid global CSS frameworks (Bootstrap full) for SaaS apps

Compression

  • gzip: ~70% reduction
  • Brotli: ~80% reduction; supported by all modern browsers
  • Configure server / CDN to serve Brotli when supported

Module federation pitfall

Module federation can cause duplicate dependency loading if shared modules are not configured. Audit when using.

Server-side rendering

SSR ships HTML before JS. Initial bundle does not need to be tiny — but hydration JS still does.

React Server Components reduce client bundle dramatically. Components run on the server; do not ship to client.

The “I do not know what is in there” problem

Many engineers do not know what their bundle contains. Steps:

  1. Run a bundle analyzer
  2. Identify the top 10 largest dependencies
  3. For each, decide: keep, replace, or remove
  4. Rebuild and verify

Often the first such pass cuts 30–50%.

Common antipatterns

  • Importing entire icon library
  • Multiple date libraries in the same bundle (moment + date-fns + dayjs)
  • Loading polyfills for every browser
  • One massive bundle (no splitting)
  • Unused legacy code (delete it)

Frequently Asked Questions

What is the biggest single optimization for most apps?

Removing moment.js. Many apps still ship it. Switching to Day.js or date-fns alone saves 100+KB.

How do I track bundle size over time?

CI step that fails the build if bundle exceeds threshold. bundlewatch, size-limit, statoscope.

What about React itself — can I make it smaller?

React + React DOM is ~140KB minified. Preact is a 3KB drop-in for ~80% of React APIs. Used by smaller apps targeting mobile.

Scroll to Top