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:
- Run a bundle analyzer
- Identify the top 10 largest dependencies
- For each, decide: keep, replace, or remove
- 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.