IAnswerable

Find What's Making Your React Bundle Huge

14 Jan 2026

Your bundle didn’t get big overnight. It got big one import at a time, each one reasonable on its own. Finding the actual offenders takes fifteen minutes with the right tool — guessing takes an afternoon.

Start with the analyzer, not a guess

For a webpack-based setup (Create React App, custom webpack, older Next.js):

npm install --save-dev webpack-bundle-analyzer

For Next.js specifically, use the built-in wrapper:

npm install --save-dev @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})
ANALYZE=true npm run build

For Vite:

npm install --save-dev rollup-plugin-visualizer

Any of these gives you a treemap: rectangles sized by actual bytes in the final bundle. Stop guessing which import is heavy — the treemap tells you.

The usual offenders, and what to do about each

Offender Typical size hit Fix
Moment.js 200-300KB Switch to date-fns or dayjs (2-15KB, tree-shakeable)
Full lodash (import _ from 'lodash') 70KB+ Import specific functions: import debounce from 'lodash/debounce'
Icon libraries importing the whole set 50-150KB Import individual icons, not the barrel export
Chart libraries (full Chart.js, full Recharts) 100KB+ Use tree-shakeable imports or a lighter alternative if you only need one chart type
Duplicate React/React-DOM versions Varies, often 40KB+ Check for multiple versions via npm ls react
A whole UI library for one component 100KB+ Copy the one component, don’t import the library

The lodash case is the clearest example of a fix with zero downside:

// 70KB+, pulls in the entire library
import _ from 'lodash'
_.debounce(fn, 300)

// A few KB, only the function you use
import debounce from 'lodash/debounce'
debounce(fn, 300)

Code-split what isn’t needed on first paint

Anything gated behind a route change, a modal, or an admin-only view doesn’t need to ship in the initial bundle:

import { lazy, Suspense } from 'react'

const AdminPanel = lazy(() => import('./AdminPanel'))

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      {showAdmin && <AdminPanel />}
    </Suspense>
  )
}

This doesn’t shrink your total JavaScript — it moves the weight out of the chunk that has to load before your page becomes interactive, which is the number that actually affects users.

Check for duplicate dependencies

A common silent bloat source: two versions of the same library, usually because one dependency pins an old version and your package manager can’t dedupe it.

npm ls react
npm ls react-dom

If you see more than one version tree, check package.json overrides/resolutions to force a single version, or update the dependency that’s pinning the old one.

What “good” looks like

There’s no universal target — a data-heavy dashboard legitimately ships more JS than a marketing page. The useful comparison is your own bundle over time: track the initial JS payload (not the total, the amount needed before interactivity) on every PR, and treat an unexplained jump as a regression to investigate, not a number to shrug at.

The takeaway

Run the analyzer before you touch anything — it tells you exactly which import is heavy, in exact kilobytes, instead of you guessing. Fix the top three offenders from the treemap, code-split what’s not needed immediately, and dedupe dependency versions. That’s usually most of the win.