React Bundle Size Audit Checklist Before You Ship to Production
A practical React bundle size audit checklist covering analysis tools, common bloat sources, tree-shaking issues, and quick wins before every production release.
A React application that felt fast in local development ships to production and immediately underperforms on mobile. The most common culprit is bundle size — a collection of small decisions that compound into megabytes of JavaScript no user asked for. By the time it shows up in a Lighthouse score, you are already dealing with a production problem.
Running a bundle audit before each significant release prevents that. This checklist covers every area worth checking, in the order that produces the fastest results.
Before You Start: Generate a Bundle Analysis
You cannot audit what you cannot see. Generate a visual bundle analysis first.
- Vite: Install
rollup-plugin-visualizer. Add it tovite.config.jsplugins. Runvite build. Open the generatedstats.html. - Webpack / CRA: Install
webpack-bundle-analyzer. Runnpm run build -- --stats, thennpx webpack-bundle-analyzer build/bundle-stats.json. - Next.js: Use
@next/bundle-analyzer. Enable it innext.config.jsand runANALYZE=true npm run build.
Keep the treemap open as you work through the checklist. Every fix should reduce the size of a visible rectangle in the map.
Dependencies and Third-Party Libraries
- Check for duplicate packages. Run
npm ls <package-name>for major libraries. Multiple versions of React, lodash, or date libraries in the same bundle are a common problem in monorepos and projects with many dependencies. - Replace moment.js with
date-fnsordayjsif it appears in the bundle.momentdoes not tree-shake;date-fnsimports only the functions used. - Audit icon libraries. Importing from
@heroicons/reactorreact-iconsat the package root pulls in every icon. Import individual icons by path instead:import { ArrowRight } from "@heroicons/react/24/solid". - Check lodash usage.
import _ from "lodash"imports the entire library. Useimport debounce from "lodash/debounce"or switch to native equivalents for simple utilities. - Look for libraries with lighter alternatives. A full charting library may be 200KB when only one chart type is needed. A date picker library may include internationalization data for every locale.
- Verify peer dependencies are not duplicated. Some dependencies bundle their own copy of React or other shared libraries. Check the bundle map for unexpected duplicates.
Tree Shaking
- Confirm named exports are used correctly. Tree shaking eliminates unused exports only when imports are static named imports.
import * as lib from "lib"defeats tree shaking for the entire module. - Check
sideEffectsconfiguration. Inpackage.json, thesideEffectsfield tells bundlers which files can be safely dropped if their exports are unused. Confirm your own packages and any in-house libraries have this set correctly. - Avoid re-exporting everything from barrel files. A
index.jsthat re-exports every component in a directory forces bundlers to include all of them even when only one is used. Prefer direct imports or split barrel files by feature.
Code Splitting
- Confirm routes are lazy-loaded. Every top-level route should use
React.lazy. Check the network panel in the browser: navigating to a new route should trigger a new chunk download. - Identify heavy components that are conditionally rendered. Modals, drawers, editors, and admin panels that appear on interaction are good candidates for splitting, especially if they import large libraries.
- Verify Suspense boundaries and error boundaries are in place. Every lazy-loaded component needs a
Suspensewrapper and an error boundary for when chunk fetches fail.
Assets and Static Files
- Check for uncompressed images imported directly into JavaScript. SVGs imported as React components are fine for small icons but can inflate the bundle if used for large illustrations.
- Confirm CSS is not inlined in JS unnecessarily. CSS-in-JS solutions that serialize styles at render time can add to JavaScript execution cost. Verify this is intentional.
- Check font loading strategy. Fonts loaded via JavaScript rather than CSS or
<link>tags can block rendering and inflate the JS parse cost.
Build Configuration
- Verify production mode is active. A React build with
NODE_ENV=developmentincludes PropTypes, development warnings, and extra checks. Confirmprocess.env.NODE_ENVis set to"production"in the production build. - Confirm minification is running. Check that the output files are minified. A Vite or Webpack production build should produce compressed output by default, but configuration overrides can disable this.
- Enable compression. Static assets should be served with gzip or Brotli compression. This is a server or CDN configuration, not a bundler setting, but it has a larger impact on transfer size than most bundler optimizations.
- Check chunk splitting strategy. By default, Vite and Webpack split vendor dependencies into a separate chunk. Confirm this is happening — vendor chunks can be cached across deployments, reducing repeat load times for returning users.
After the Audit
- Record the baseline bundle sizes before starting. Compare main chunk size and total transfer size after each fix.
- Run Lighthouse in incognito on a throttled connection. A 150KB reduction on desktop may save 3 seconds on a mobile 3G simulation.
- Set a bundle size budget. Most bundlers support size limits in configuration. Failing the build when a chunk exceeds a threshold prevents regressions from accumulating silently.
A bundle audit is most useful as a recurring practice — run it before major releases and any time a new heavy dependency is added to the project.
If your team needs hands-on help reducing bundle size and improving load performance, Clixo does full-stack product engineering, including build optimization and architecture review.