Does Minifying Still Matter With Brotli? 6% for CSS, 36% for JS
Minifying CSS saves 6% after Brotli. Minifying JS saves 36%. The difference is one step compression cannot do: mangling.
Table of contents
- Key takeaways
- The measurements
- Why is the JavaScript number so much bigger?
- Then why minify CSS at all?
- What this means for your build
- When minification is the wrong tool entirely
- Where the numbers came from
- Frequently asked questions
- Should I minify if my server already uses gzip?
- Is Brotli always better than gzip?
- Does minifying help Core Web Vitals?
- Why is minified code hard to debug in production?
- Does minification break anything?
- Related reading
- References
The argument against minifying goes like this: compression already removes repetition, whitespace is the most repetitive thing in a file, so gzip or Brotli has eaten the win before minification can claim it. It sounds right, and for one of the two file types it is very nearly correct.
Measured on real files, the answers are 6% and 36%.
Key takeaways#
- Minifying CSS saved 6.4% of transferred bytes under Brotli. Almost nothing.
- Minifying JavaScript saved 36.0% under Brotli. Very far from nothing.
- The step that beats compression is identifier mangling, worth about 20 of those 36 points.
- Stripping comments and whitespace alone was worth 16.2%;
--compresstransforms added under 1 point on top. - CSS has no identifiers to mangle, which is the whole explanation for the gap.
The measurements#
Two files, both real. For CSS, this site's own built stylesheet, compared against itself run through Prettier to expand it back out. For JavaScript, the react-dom 19.2.8 development build — a million bytes of genuine library code with genuine comments — minified with terser 5.49.0. Compression is Brotli 1.2.0 at quality 11 and gzip at level 9.
CSS first:
| Variant | Raw | gzip | Brotli |
|---|---|---|---|
| Expanded | 134,179 | 18,866 | 15,530 |
| Minified | 105,357 | 17,290 | 14,531 |
| Saved | 21.5% | 8.4% | 6.4% |
Minification removed a fifth of the raw file and 6% of what actually crosses the network. The sceptical argument is basically right for CSS.
Now the same question for JavaScript:
| Variant | Raw | Brotli |
|---|---|---|
| Original | 1,065,698 | 135,348 |
| Minified | 349,385 | 86,643 |
| Saved | 67.2% | 36.0% |
Thirty-six percent of transferred bytes, after Brotli has already done its best. That is not a rounding error, and it is not explained by whitespace.
Why is the JavaScript number so much bigger?#
Because minifying JavaScript does something compression cannot: it renames things. Compression finds repeated byte sequences and refers back to them, so a long identifier repeated 200 times costs almost nothing after the first occurrence. But it still costs something every time, and a two-character name costs less.
Splitting the minification into stages shows where that win comes from. Each row is cumulative Brotli-transferred savings against the un-minified original:
| Stage | Raw | Brotli | Saved |
|---|---|---|---|
| Original | 1,065,698 | 135,348 | — |
| Comments and whitespace only | 688,746 | 113,373 | 16.2% |
--compress transforms only | 676,199 | 112,225 | 17.1% |
--mangle identifiers | 352,985 | 86,974 | 35.8% |
| Everything | 349,385 | 86,643 | 36.0% |
Read the third and fourth rows together. Dropping comments and reformatting gets 16%. Adding the syntactic transforms --compress performs — collapsing if statements, dropping dead branches, shortening boolean expressions — adds less than one percentage point once Brotli is involved, because those transforms mostly remove structure that compressed well anyway.
Mangling doubles the saving on its own.
That also explains the CSS result rather than leaving it as a separate fact. A CSS minifier has no local scope to exploit: class names are a public contract, custom property names are referenced by name, and selectors cannot be renamed without rewriting the HTML. So CSS minification is whitespace and comment removal, which is precisely the part compression already handles. Same mechanism, opposite conclusion, and one theory covering both.
Then why minify CSS at all?#
Because transferred bytes are not the only cost, and for CSS they are the smallest one.
The raw file is what the browser actually parses. 105 KB of CSS parses faster than 134 KB, decompresses faster, and occupies less memory in the stylesheet object model. On a mid-range phone that gap is real even though the download difference is a rounding error. Render-blocking CSS is the one resource where parse time sits directly on the critical path to first paint.
So the honest framing for CSS is: minify it, but do not count the saving as a network win. It is a parse-time win worth roughly 20% of your stylesheet, and if your stylesheet is 12 KB that is not worth an afternoon of tooling work.
What this means for your build#
Ordered by how much it changes:
- Minify JavaScript, and make sure mangling is on. It is the single largest transfer saving in the list, and it is a default in every modern bundler — worth confirming rather than assuming, because a misconfigured
terserOptionsthat disables mangling looks like it is working. - Serve Brotli, not just gzip. Brotli beat gzip by 16% on the minified CSS and stays ahead on JavaScript. This is a server or CDN setting, not a build step.
- Minify CSS. Cheap, automatic, and the win is parse time rather than bandwidth.
- Do not hand-minify anything. Minification is a build-time transform. A minified file in source control is a file nobody can review.
- Stop worrying about
--compresssettings. Under Brotli they are worth under a point. Aggressive compress options are where minifier bugs live, for almost no measurable return.
Point 5 is the counterintuitive one: the riskiest minifier settings buy the least.
When minification is the wrong tool entirely#
None of the above touches the bigger question, which is whether the bytes need to ship at all. Minification is a constant-factor improvement on a file you have already decided to send.
A 350 KB minified bundle that could have been 90 KB with code splitting is not a minification problem. Neither is a date library imported whole for one function, an icon set imported by barrel file, or a polyfill for a browser nobody uses. Those are all order-of-magnitude wins, and minification is a 36% one.
The order that actually matters: send less code, then split what remains, then minify, then compress. Minification is third for a reason.
Where the numbers came from#
Reproducible, and worth reproducing on your own assets, because the CSS-versus-JavaScript ratio in your bundle decides which of the two answers above applies to you:
# JavaScript: minify with terser, then compare compressed sizes
npx terser src.js --compress --mangle -o src.min.js
brotli -q 11 -c src.js | wc -c
brotli -q 11 -c src.min.js | wc -c
# Isolate the mangling win
npx terser src.js -o strip.js # comments and whitespace only
npx terser src.js --mangle -o mangle.js # plus identifier renamingFor a quick check on a single file without a build step, the JavaScript minifier and CSS minifier run in the browser, which is the point when the file is a vendor script you would rather not upload anywhere. The HTML minifier is the third case, and the one with the smallest return of the three: HTML is mostly tag names and attribute values, both highly repetitive and both handled well by compression.
Frequently asked questions#
Should I minify if my server already uses gzip?#
Yes for JavaScript, where minification saved 36% of transferred bytes on top of Brotli and a similar order on top of gzip. For CSS the transfer saving is around 6–8%, and the reason to do it is parse time rather than bandwidth.
Is Brotli always better than gzip?#
For static text assets compressed ahead of time, yes — 14,531 bytes versus 17,290 on the stylesheet measured here. For dynamic responses compressed per request the trade-off is CPU, which is why servers use a lower Brotli quality there and the gap narrows.
Does minifying help Core Web Vitals?#
Indirectly and mostly through parse time. Smaller JavaScript shortens the main-thread work that delays interaction readiness, and smaller render-blocking CSS shortens the path to first paint. Neither is measured directly by any Web Vital.
Why is minified code hard to debug in production?#
Because mangling is what makes it small, and mangling destroys the names a stack trace needs. Source maps restore them. Generate them, and serve them somewhere your error reporter can read but the public cannot.
Does minification break anything?#
Rarely, and almost always in the aggressive --compress options rather than mangling — code relying on Function.prototype.name, on toString() of a function, or on property names surviving. Since those options are worth under a point after Brotli, the safe default is also the well-performing one.
Related reading#
- Core Web Vitals — what the parse-time saving actually moves
- Next.js Image Optimization
- INP and the 200ms Threshold
- Minify a single file in the browser with the JavaScript Minifier, CSS Minifier or HTML Minifier, and reverse it with the JavaScript Formatter.
References#
- terser — the minifier used for the JavaScript measurements
- Brotli
- web.dev: reduce JavaScript payloads with code splitting
Tags
- Performance
- Minification
- Compression
- JavaScript
- CSS
