Skip to content
Free Tool Arena

Developers & Technical · Guide · Developer Utilities

How to minify JavaScript

What JS minification actually does, Terser vs esbuild vs SWC, source maps, bundler integration, and the patterns that break minified code.

Updated April 2026 · 6 min read

Minifying JavaScript shrinks bundle size, speeds up page loads, and reduces the bytes users download on slow connections. Done right, it’s invisible — same behavior, fewer kilobytes. Done wrong, it breaks your app in production. This guide covers what minification actually does (whitespace removal, variable renaming, dead code elimination), the difference between minification and compression, when to use Terser vs esbuild vs SWC, source maps for debugging minified code, the gotchas that break minified bundles, and how modern bundlers handle all of this automatically.

Advertisement

What minification actually does

A minifier reads your JavaScript source and outputs semantically equivalent but smaller code. The transformations include:

Whitespace removal: strip unnecessary spaces, tabs, and newlines. Typically saves 10-20%.

Variable renaming (mangling): rename totalUserCount to a. Works only on local scope variables, not exports or properties. Saves 30-50% on identifier-heavy code.

Dead code elimination: remove unreachable branches, unused imports, and dev-only code gated by process.env.NODE_ENV !== 'production'.

Constant folding: 2 + 3 becomes 5. "foo" + "bar" becomes "foobar".

Short-form rewrites: true becomes !0; undefined becomes void 0. Saves bytes, hurts readability — but readability doesn’t matter in production.

Minification vs compression

Minification and gzip/brotli are complementary, not redundant.

Minification removes redundancy visible to the JS parser.

Compression (gzip, brotli) removes redundancy visible to a byte-level algorithm.

Typical results: 100KB raw JS → 40KB minified → 12KB brotli. Both matter. Minification matters more for parse time (engine does less work on smaller input); compression matters more for transfer time.

Source maps — debug minified code

Minified code is unreadable. A source map maps minified positions back to original source, so browser devtools can show you the original file when debugging.

Inline: //# sourceMappingURL=data:application/json;base64,... embedded in the JS file. Increases file size; keep for dev only.

External: //# sourceMappingURL=app.js.map as a sibling file. Browser fetches only when devtools is open.

Hidden source maps: generate the map but don’t reference it in the JS. Upload to Sentry/Datadog/ Rollbar for error tracking without exposing source publicly.

Don’t ship source maps referencing public URLs unless you want source visible. Obvious, but common mistake.

Terser, esbuild, SWC — picking a minifier

Terser: the default for most bundlers. Written in JS, slow but produces the smallest output. Forked from UglifyJS, handles modern ES syntax.

esbuild: written in Go. 10-100x faster than Terser. Output is slightly larger (a few percent). Tradeoff is usually worth it for dev iteration speed.

SWC: Rust-based minifier used by Next.js and others. Fast like esbuild, output comparable to Terser. Now the default in most modern toolchains.

Rule: use whatever your bundler defaults to. The differences are small; the effort of switching rarely pays off.

Common breakages from minification

String-based property access: obj["longName"] vs obj.longName. Minifier mangles the latter to obj.a; leaves the string intact. Your code crashes.

Reflection on class/function names: SomeClass.name might return "a" after mangling. Affects routing/DI systems that rely on class names.

Angular-style DI with string params: older Angular code declared deps as strings matching function parameter names. Mangle breaks the match. Fixed by using array-form annotations or ngAnnotate.

Side-effectful module imports: tree shaking might drop imports that run code on load. sideEffects: false in package.json tells the bundler it’s safe to remove unused imports.

Dynamic eval: minified code inside eval can break if the evaluated code references symbols that got renamed. Avoid eval.

What minifiers can’t help with

Duplicated code: copy-pasted logic across files stays as two separate copies after minification (no dedup across scopes).

Large dependencies: importing moment.js costs ~70KB minified. Minifier can’t shrink third-party code you don’t own.

Tree shaking limits: only ESM imports tree shake cleanly. CommonJS require() is dynamic; bundler can’t always prove what’s unused.

Configuring minification

Most tuning happens through bundler config. Common Terser options worth knowing:

drop_console: remove console.log calls in production.

drop_debugger: strip debugger statements.

pure_funcs: mark functions as side-effect-free so calls get removed if return value unused.

reserved: names never to mangle (e.g., DI tokens, global API surfaces).

keep_classnames / keep_fnames: preserve names for reflection-based code.

Bundlers do this for you

Webpack: minifies in production mode by default with Terser.

Next.js / Vite / Parcel: minify production builds automatically.

esbuild: --minify flag or minify: true in config.

You rarely need to run a standalone minifier. The exception: you’re shipping a single file or inline script that isn’t part of a build pipeline.

Common mistakes

Shipping unminified code. Check your build output. If it’s readable, it’s not minified.

Shipping source maps publicly. Attackers can reverse-engineer your code. Use hidden source maps uploaded to an error-tracking service.

Minifying dev builds. Makes debugging painful. Use production mode only for production.

Forgetting to enable compression at the server.Minification without gzip/brotli leaves 70% of savings on the table.

Relying on class/function names for behavior.Minifier breaks this silently. Use explicit tokens or disable mangling selectively.

Run the numbers

Minify JavaScript instantly in your browser with the JS minifier. Pair with the CSS minifier for stylesheet shrinkage, and the HTML formatter to clean up markup before minifying.

Advertisement

Found this useful?Email