Guide
Minifying JavaScript, CSS and HTML for production
Minifying isn't one operation — a JavaScript minifier, a CSS minifier and an HTML minifier are solving different problems with different amounts of risk, and "safe" only means safe with respect to what each one actually understands about the language it's minifying.
What actually changes, per language
JavaScript. A modern minifier — this site's JS tool uses Terser — parses the source into a syntax tree rather than pattern-matching text, which is what makes it safe to do more than strip whitespace. Terser's defaults both compress (removing dead branches, folding constants, shortening some expressions) and mangle: every local variable and function name gets renamed to something shorter, unless the code relies on that name being preserved.
CSS. This site's minifier, clean-css, strips whitespace and comments, shortens color and length values where the shorter form is exactly equivalent, and merges rules with duplicate selectors. One deliberate exception: a comment that begins /*! is preserved even through minification — that's the convention for a licence banner, and stripping it silently would lose attribution the same way stripping whitespace never does.
HTML. HTML minification is really three minifiers wearing one coat: this tool collapses insignificant whitespace between tags, removes comments, and — because a real HTML page usually embeds both — also runs the CSS minifier over inline <style> blocks and the JS minifier over inline <script> blocks. Minifying HTML with embedded code touches all three languages in the same request.
Why AST-based minification is safe where regex-based tricks were not
Early, naive JS "minifiers" were often little more than whitespace-stripping regexes, and that approach is genuinely dangerous in JavaScript because of automatic semicolon insertion (ASI): the language will insert a semicolon for you at a line break in some situations, which means removing a newline can silently change where one statement ends and the next begins.
return
{ value: 1 };
// ASI inserts a semicolon right after `return`, so this is actually:
return;
{ value: 1 }; // an unreachable, unused block — not what was written
A minifier that understands the grammar — parses to a tree, transforms the tree, re-prints from the tree — never hits this class of bug, because it never depends on where a line break happens to be; it works from the parsed meaning of the code. That's the real difference between "safe" and "risky" minification, and it's why the tooling ecosystem moved from clever regexes to real parsers.
What still breaks, and why it isn't the minifier's fault
- Name mangling meeting reflection. Code that reads
fn.name, or that looks up a class by its string name for dependency injection or serialization, breaks when that name gets mangled to something liket. This is a real and common failure mode in frameworks that do runtime reflection — the fix is telling the minifier which names to leave alone, not disabling mangling entirely. - Global names referenced as strings. Anything that does
window['someGlobal']instead ofwindow.someGlobalsurvives minification of the property access but breaks ifsomeGlobalwas also a local declaration that got renamed — the string literal has no way to know it was supposed to track a renamed identifier. - CSS specificity surprises from rule merging. Merging two rules with identical declarations to reduce output size is safe for the computed styles of the selectors involved, but can change which rule "wins" for an element matched by more than one of the merged selectors if the merge also changes source order — worth spot-checking on a page with deliberately overlapping, order-dependent selectors.
- Whitespace-sensitive HTML elements. Inside
<pre>and<textarea>, whitespace is part of the displayed content, not just source formatting — a minifier that collapsed it there would visibly change the page. This tool's HTML minifier already knows to leave those elements alone; it's worth knowing why, since a hand-rolled minifier that didn't would look fine until someone pasted preformatted text through it.
Source maps: what a one-shot minifier like this one can't give you
A source map is a small JSON file that records, roughly, "this line and column in the minified output corresponds to that line and column in the original source." With one loaded, browser devtools can show you a stack trace, and let you set breakpoints, against the original code even though the browser is actually running the mangled, single-line version — production crash reports become readable instead of pointing at t.js:1:48213.
That mapping has to be generated in the same pass that does the minifying, from the same parse tree, which is why it's a feature of a build pipeline — webpack, esbuild, Vite, Rollup — rather than something a stateless "paste code, get minified code back" API can produce: there is no original-source file identity to map back to once the request is just a string. This tool is well suited to a one-off "minify this snippet" or "clean up this file I was handed" task; a real application build should go through a bundler that emits matching source maps alongside its minified output, not treat a tool like this as a substitute for that step.
Does it still matter, with gzip and brotli doing the heavy lifting?
Compression narrows the gap minification used to close on its own — repeated whitespace and long identifier names are exactly the kind of redundancy gzip and brotli are good at squeezing out, so the wire-size difference between minified-then-compressed and unminified-then-compressed is smaller than the raw minification ratio alone suggests.
Two reasons it's still worth doing regardless. First, parse and compile time: the browser's JS engine has to tokenize and parse every byte you send it before compression is even relevant to that cost — fewer bytes and simpler tokens (short names instead of long ones) means less work before your code runs at all, and that cost isn't touched by transport compression at all. Second, CSS and HTML minification remove things compression can't always reach as effectively — merged duplicate rules and stripped comments reduce the actual parsed rule count and DOM-adjacent overhead, not just the transmitted byte count.
Reading minified code you were handed
Beautify is the same tool in reverse, and it earns its keep in a specific situation: someone hands you a minified bundle with no source map — a third-party script, a vendored file with a stripped licence, something recovered from a production incident — and you need to read it well enough to understand what it does. Re-indenting restores structure a human can follow; it does not restore the original names, which is a fundamentally different, harder problem than formatting.
If the code you're looking at doesn't just look minified but actively resists being understood even after beautifying — renamed to meaningless hex identifiers, strings pulled into an encoded lookup array, control flow rebuilt as an opaque dispatch loop — that isn't minification doing its normal job. See the companion guide on JavaScript obfuscation for what that additional layer is actually doing and why beautifying alone won't undo it.
Last updated 21 August 2026