JavaScript is the second-biggest thing most pages download — so shrinking the bytes on the wire is one of the highest-leverage wins. It happens in two stages: minification rewrites your code smaller, then HTTP compression (gzip / brotli) squeezes it further for the trip to the browser. This lesson covers both — and the trade-offs that can quietly cancel them out.
Stage 1
Minification
Minification makes the code itself smaller while keeping it valid JavaScript the engine runs identically. It strips comments and whitespace, drops dead code, and — the big one — mangles identifiers, renaming your firstNumbers to a. Step through it:
InteractiveThe same function, minified
Minification: source → wire-ready
// add two numbers together
function addNumbers(firstNumber, secondNumber) {
const result = firstNumber + secondNumber;
return result;
}
140 B
1/4 · Source
The code you write — comments, spaces, descriptive names. Readable, but heavy.
Comments → whitespace → mangled names. The JS engine runs all four identically; only the byte count changes.
Stage 2
HTTP compression
Minified code is still plain text full of repetition — perfect for a compression algorithm. Compression comes in two flavours: lossy (throws away detail you won't miss — great for JPEGs, ~50% smaller) and lossless (decompresses to a byte-perfect copy). Text must be lossless — a missing character would break your code — so HTML, CSS, and JS always use lossless algorithms like gzip and brotli.
Lossy is for media, lossless is for code
A JPEG can lose detail nobody notices. A semicolon can't. That's why images use lossy compression and text assets use lossless.
The two algorithms
gzip vs brotli — level vs speed
gzip is the ~30-year-old standard, supported literally everywhere. brotli (2015) builds on the same ideas (LZ77 duplicate-detection + Huffman coding) with a bigger window and a built-in dictionary, so it compresses tighter. Both expose a level: higher = smaller file but more CPU time, with diminishing returns at the top. Play:
InteractiveCompression ratio vs. compression speed
5
Original (minified)100.0 KB
After brotli -527.8 KB
Compression ratio
3.60×
Over the wire
27.8 KB
Compression speed
fast
Sweet spot. Mid-level brotli beats gzip's best ratio while keeping compression fast enough.
Crank the level and watch the file shrink — but the top levels barely help while costing real CPU. brotli's mid-range beats gzip's best.
That was a model — this is the real thing. The box below runs your browser's own CompressionStream API on whatever you put in it. Paste your actual bundle if you like; the byte counts are genuine:
InteractiveCompress your own code — real gzip, in this tab
This is not a simulation: the button below runs your browser’s own CompressionStream on the text in this box. Paste your own code if you like.
No simulation: the text is gzipped and deflated by the browser itself. Repetitive code compresses absurdly well — exactly why minify-then-compress is the right order.
When to compress
Static vs dynamic compression
Static (build-time) compression runs once when you build, saving a pre-compressed file on the server. Since it's offline, you can afford the highest levels — CPU cost doesn't touch request time. Best for assets that don't change often (your JS bundles). Dynamic (on-the-fly) compression runs per request in the server / proxy (nginx, Apache). Easier to set up and great for frequently-changing content — but you must use a lower level, or the compression time itself slows the response you were trying to speed up.
Static (build-time)
Dynamic (on-the-fly)
When
Once, during build
Per request, at the server
Level you can afford
Highest (offline)
Lower (adds latency)
Best for
Stable bundles
Frequently-changing content
Done by
Bundler plugin
nginx / Apache / proxy
How the browser knows
The browser/server handshake
You never decompress anything by hand. The browser advertises what it supports, the server picks one and labels the response, and the browser decompresses transparently before your code runs:
InteractiveAccept-Encoding → Content-Encoding
🖥
Browser → Server
Accept-Encoding:gzip, deflate, br, zstd
“Here's what I can decompress.”
↓
🗄
Server → Browser
Content-Encoding:br
“I compressed it with brotli.”
The browser advertises what it supports; the server picks one and says which it used. The browser then does the decompression — transparently, before your JS ever runs.
The request says what the browser can handle; the response says what the server used. Decompression happens in the browser, automatically.
To check it's actually working: a Lighthouse audit flags “enable text compression” with the potential savings, and the DevTools Network tab shows the Content-Encoding header on each response.
The catch
Trade-offs with splitting & caching
Here's where the techniques in this module collide. Compression works better on bigger files — more repeated patterns to exploit. So aggressive bundle splitting into many tiny chunks can actuallyhurt the compression ratio. But a giant merged bundle hurts caching: change one line and the whole file's cache is invalidated, while split chunks let unchanged ones stay cached.
The three-way balance
Splitting (better caching + smaller initial load) vs. merging (better compression). The rule of thumb: split things that aren't related (separate routes), keep things that are related together. There's no universal answer — it depends on your app's change patterns and navigation.
Q1Multiple choice
A teammate says 'we gzip everything, so minification is redundant.' Why are they wrong?
Q2Multiple choice
Your server applies max-level Brotli to every dynamically-rendered response. P95 latency rises. The better policy?
Q3Multiple choice
Why can over-aggressive bundle splitting hurt compression?
Q4Sort each scenario
Which stage does each belong to — minification (build, transforms code) or HTTP compression (transport, byte-level)?
Renaming userCount to `a` (mangling)
Applying Brotli over the wire via Content-Encoding
Stripping comments and dead code
Negotiating the algorithm with Accept-Encoding
Shrinking the code's AST while keeping it valid JS
Key takeaways
→Two stages: minification (smaller code) then HTTP compression (smaller transfer).
→Minification strips comments/whitespace/dead code and mangles names — still valid JS.
→Text uses lossless compression; brotli generally beats gzip at a given size.
→Higher levels = smaller but slower; use high levels static (build), low levels dynamic (per-request).
→Mind the three-way trade-off: splitting (caching) vs merging (compression).