Skip to main content
AllDevToolsHub
📊

Bundle Size Analyzer

100% Local

Upload stats.json and inspect top assets by size.

Bundle Size Analyzer

Bundle size analyzer

Upload a Webpack/Rollup stats.json (or similar) and see largest assets. Runs locally.

Try:

Privacy note

This tool runs entirely in your browser. Your input is never uploaded, logged, or sent to AllDevToolsHub or anyone else, and it keeps working offline once the page has loaded.

How to Use Bundle Size Analyzer

01

Paste Bundle Stats

Upload a webpack stats.json or paste the output of a build analysis.

02

View Breakdown

See which modules and dependencies take up the most space in your bundle.

03

Identify Bloat

Find duplicate packages, oversized dependencies, and tree-shaking opportunities.

Bundle Size Analyzer: the essentials

The Bundle Size Analyzer parses Webpack, Rollup, Vite, or esbuild stats output and ranks your largest emitted assets by size, with totals and per-chunk breakdowns. Stats files stay local, useful when triaging bundle bloat without uploading proprietary build artifacts containing internal package names, monorepo paths, or feature flags to a third-party service.

Key points

  • Processes configuration and code locally, your project data never leaves your browser.
  • Validates against common standards and best practices for the domain.
  • Works offline once loaded, no active internet connection required for processing.
Overview

What is Bundle Size Analyzer?

Parse common Webpack and Rollup stats formats to list the largest output assets with totals. All processing stays on your device, no uploads.
FAQ

Frequently Asked Questions

Reference

Technical Deep Dive

DEVELOPMENT TOOLS

Bundle Size Analyzer

Parse common Webpack/Rollup stats formats and list the largest output assets with totals. Data stays on your device.

📦

Reads webpack/Vite stats

Load the stats.json your bundler already emits (--json, rollup-plugin-visualizer, vite build) — no build integration to wire up.

📊

Ranked by real cost

Assets and modules are sorted by parsed and gzipped size, so the 400 KB moment library shows up above the 2 KB helpers.

🔒

Local parsing

The stats file — which leaks your dependency tree and internal module paths — is parsed in memory and never sent anywhere.

Reading a Bundle Without the Treemap

The default webpack-bundle-analyzer UI shows a beautiful interactive treemap, and Rollup's visualizer plugin does similar. They are wonderful for the first time you look at a build, orient yourself, find the big rectangles, click around. But for the workflow that actually matters, "what's the biggest thing in this build, is it bigger than last week, and is the new entry in my top 10 a regression I should block", a sorted asset list with totals is faster, smaller, and easier to diff in CI. That's what this tool gives you.

What's Actually in a stats.json

A Webpack stats file has dozens of top-level keys; for size analysis, three matter:

  • assets, every file emitted to disk, with name, size (bytes), and chunks (which logical chunks produced it). This is what hits the user's browser; this is what costs them transfer, memory, and parse time.
  • chunks, the logical groupings before files are emitted. A single chunk can produce multiple assets (a JS file, a corresponding CSS file, a sourcemap, sometimes a runtime helper). Each chunk has names, size, modules, and dependency edges.
  • modules, every source module pulled into the build, with size, source path, the chunks that include it, and (often) the reasons it was included. This is where you find which npm package is bloating which chunk.

This tool ranks the assets array because that's the ground truth for user-visible cost. To dig deeper into a single asset, "why is main.js 800KB?", cross-reference its chunks field against chunks[].modules to see the modules inside it, then group those modules by their npm package (the first node_modules/ path segment).

Rollup, Vite, and esbuild, Same Idea, Different Schemas

Rollup (and Vite, which uses Rollup for production builds): the visualizer plugin emits JSON with a top-level tree (nested module sizes by directory) and nodeParts (per-id parsed/gzipped sizes). The semantics differ slightly from Webpack, Rollup has chunks but no separate assets concept, because the chunk-to-file mapping is 1-to-1 for the JS itself. CSS is emitted as a side effect.

esbuild: the metafile: true output is a single object with inputs (every module that went into the build, with its size and which inputs it imports) and outputs (every file emitted, with its size, the inputs that contributed, and the bytes from each). The bytes-from-each input data is genuinely useful for working out which input contributed how many bytes to which output, esbuild is the only mainstream bundler that gives you this directly.

This tool normalizes all four formats to the same ranked-asset view, so the cross-bundler comparison ("we're moving from Webpack to Vite, did the bundle get bigger?") works.

The Usual Suspects When a Bundle Grows

When a bundle suddenly grows, the culprit is almost always one of:

1. Barrel imports of icon libraries. import { Icon } from 'lucide-react' works syntactically, but unless your bundler can tree-shake the index file and the library ships side-effect-free ESM with per-icon exports, you ship the whole set. Lucide is ~300KB total; you probably wanted three icons. Fix: import the specific module, import Icon from 'lucide-react/dist/esm/icons/specific-icon', or use a bundler-aware wrapper like unplugin-icons that resolves per-icon imports at compile time.

Same pattern hits: @mui/icons-material (each icon as a separate import path), react-icons (use the per-pack imports: react-icons/fa/FaHome not react-icons), @chakra-ui/icons, etc.

2. moment.js with all locales. Moment bundles every locale by default, ~250KB. The fix is either (a) new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }) to drop them, or (b) migrate to date-fns (tree-shakes per-function), dayjs (~2KB core), or the native Intl.DateTimeFormat and Intl.RelativeTimeFormat APIs.

3. Lodash imported as a whole. import _ from 'lodash' pulls all 280+ utilities. import { debounce } from 'lodash' is better but still ships the whole package on older configs; import debounce from 'lodash/debounce' or switching to lodash-es (and trusting your bundler's tree-shaker) is best.

4. Polyfills doubled up. core-js plus a framework's built-in polyfills. Check your browserslist config and @babel/preset-env targets, if you're targeting > 0.5% you're polyfilling for IE11 corners that no longer exist. browserslist: ['defaults and supports es6-module'] cuts polyfills dramatically.

5. Source maps shipped to production. A .map file is harmless if it's only referenced by a //# sourceMappingURL= comment and not preloaded, but if it appears in the assets list as part of an entry chunk, or if your Next.js config has productionBrowserSourceMaps: true and a CDN that aggressively preloads, you're shipping debug data to every user.

6. Heavy charting / visualization libraries left as static imports. recharts is 200KB+, d3 70KB+, apexcharts 400KB+, echarts 700KB+, chart.js ~200KB. If only the dashboard route uses them, they should be behind React.lazy(() => import('./Chart')) and a Suspense boundary.

7. PDF libraries. pdf-lib is 400KB, jspdf is 600KB+, pdfjs-dist is ~2MB and worth its own conversation. None of these should be in the main bundle.

8. Editor libraries. Monaco (VS Code's editor) is ~3MB. CodeMirror v6 is more svelte (~200KB for a basic setup) but still substantial. Both must be lazy-loaded.

9. A dependency that quietly bundled its own version of react / react-dom. Rare but vicious, it doubles your framework size and breaks hooks. Find it with npm ls react (multiple results = problem).

10. The icon font that nobody removed. Bootstrap glyphs, Font Awesome 4, ionicons, often left in a layout from project bootstrap and never removed. Search assets for .woff2 and .ttf and audit.

What "Size" Means, A Three-Layer Number

When the analyzer shows "main.js: 480KB", what does that number represent?

  • On disk / parsed size: 480KB. What V8 holds in memory after decompression.
  • Wire / gzipped size: typically 120–160KB. What the user's browser actually downloads.
  • Brotli size: typically 100–135KB. What modern CDNs (Cloudflare, Vercel, Netlify) serve to modern browsers.

The number this tool shows by default is parsed because that's what's in the stats file. To get gzipped sizes natively in the bundler:

  • Webpack: performance: { hints: 'warning', maxAssetSize: 250000 } warns on raw size. For gzipped, add compression-webpack-plugin.
  • Vite: build.reportCompressedSize: true (the default) prints both raw and gzipped to console at build time.
  • Next.js: gzipped sizes appear in the next build summary table per route.

Don't confuse them when setting budgets. "<170KB JS for first paint" is almost always gzipped.

Parse Cost. The Forgotten Half of Bundle Cost

A 500KB-gzipped (≈2MB parsed) JS bundle takes ~1500ms to parse and compile on a mid-tier 2020 Android device. The download might take 800ms. Total time-to-interactive: ~2.5 seconds before any of your code runs. This is why "fast 4G" benchmarks underestimate the problem, they measure transfer, not execution.

V8's parse cost is roughly linear in parsed bytes. Trimming a 200KB unused dependency saves ~300ms of parse time even on a desktop, more on mobile. Lazy-loading the same dependency moves that parse cost to whenever the user clicks the feature, much more often, "never."

This is the strongest argument for code-splitting routes, lazy-loading modals, and dynamic-importing heavy libraries: not download time, but parse and execute time on devices that aren't yours.

Diff Workflow. The Most Useful Daily Use

The single highest-value workflow for this tool is diffing two builds:

  1. Build main branch → save stats.main.json.
  2. Build feature branch → save stats.feature.json.
  3. Open both in this tool, sort by size, compare top 10.

You're looking for:

  • New assets in the feature build that aren't in main → did you intentionally add a chunk?
  • Existing assets that grew >5KB → which feature caused it? Was the cost expected?
  • Existing assets that shrank → did a refactor accidentally also remove an import you needed at runtime?

Wire this into your PR review checklist; bundle size regressions are far cheaper to catch in review than after rollout.

Treemap vs. Ranked List, Picking the Right Tool

Use the interactive treemap (webpack-bundle-analyzer, rollup-plugin-visualizer with default template, esbuild-visualizer) when:

  • It's a new codebase and you don't yet know what's in it.
  • You're investigating a specific large asset and need to see the module hierarchy.
  • You're hunting duplicate dependencies and need to see two copies side-by-side spatially.

Use a ranked asset list (this tool, size-limit, bundlemon) when:

  • You already know the codebase and you're checking for regressions.
  • You're scripting CI gates.
  • You're comparing two builds.
  • You're sharing a snapshot of "what's expensive in our build" in a Slack thread.

Most teams use both, at different cadences.

Privacy Note, Why Local Parsing Matters Here

All parsing happens in your browser via the File API. The stats.json never leaves your machine, important because it routinely contains:

  • Absolute filesystem paths: /Users/jane.doe/projects/acme-corp-internal/... reveals your username, employer, and project location.
  • Internal monorepo package names: @acme/billing-engine, @acme-internal/feature-flags-prod, names that hint at product structure, often subject to NDA.
  • Feature flag identifiers: routes and chunks named after unreleased products (stripe-paymentintents-v2, darkmode-redesign) that you'd rather not telegraph.
  • Full dependency graph: every npm package and version in your app, including any private registry packages.
  • Source paths into your project: revealing the structure of your codebase down to file granularity.

Uploading any of this to a third-party "free bundle analyzer" website is a routine, quiet form of data exfiltration. The tool that lives in your browser tab and never makes a network request avoids all of this.

Common Mistakes

  • Optimizing without measuring. Replacing lodash with native methods saves ~5KB per debounce; tree-shaking moment saves 250KB. Pick the one you actually have.

  • Confusing parsed and gzipped budgets. A 170KB parsed budget is brutally tight; 170KB gzipped is reasonable for a modern web app.

  • Treating bundle size as a one-time project. Bundle size only grows; the codebase only adds, rarely deletes. A monthly bundle audit and a CI gate are how you keep this from creeping.

  • Optimizing the wrong entry point. Most users never see your admin route. Optimize the route 95% of users land on first.

  • Adding dynamic imports for things that load on first interaction anyway. A modal that 70% of users open within 5 seconds of page load gains nothing from being a separate chunk; you've added a network roundtrip for a non-deferral.

  • Forgetting that node_modules can also be split. Webpack's defaults split some vendor code but leave plenty inlined; an explicit splitChunks.cacheGroups.vendors rule with reasonable thresholds is one of the highest-leverage configs you can write.

You Might Also Need