The text you download from a website travels compressed to 20-30% of its original size. That invisible compression —negotiated between browser and server in one header— is among the highest-impact optimizations per effort invested, yet many projects still have it misconfigured or disabled. Here's the full mechanism and its fine-tuning.
Negotiation in two headers
The whole protocol fits in four lines:
# The browser announces support, in priority order:
Accept-Encoding: gzip, deflate, br, zstd
# The server answers with its choice:
Content-Encoding: br
If the server doesn't respond with Content-Encoding, the resource travels uncompressed — exactly what to audit when you suspect something's off. Modern browsers support all four: gzip (universal), deflate (zlib, rare today), br (Brotli) and zstd (Zstandard, the newcomer).
The shared engine: LZ77 + Huffman
Gzip and Brotli share two fundamental ideas inherited from DEFLATE:
LZ77 (repetition substitution): when a previously seen sequence appears again, emit a backward reference —"copy 25 bytes from 340 positions ago"— instead of repeating it. HTML/CSS/JS repeat constantly: tags, class names, keywords, selectors.
Huffman (variable-length codes): frequent symbols get short codes, rare ones long. The letter "e" may cost 4 bits; an uncommon character 15.
The difference between formats lies in how much each piece gets optimized:
| Gzip | Brotli | |
|---|---|---|
| LZ77 window | 32 KB | up to 16 MB |
| Huffman codes | dynamic per block | multiple contexts |
| Built-in dictionary | No | Yes (120 KB common web) |
| Typical ratio vs gzip | reference | 15-25% smaller |
Brotli's large window matters for big files: a repetition 500 KB apart is invisible to gzip but caught by brotli. And the built-in dictionary —frequent HTML, CSS, JS fragments and common English words— means even a tiny 2 KB file compresses as if half of it had been seen before.
Zstd adds another dimension: near-brotli ratios with much faster compression speed, ideal where CPU matters (CDNs re-compressing on the fly). Its web adoption is growing fast.
Compression levels: the real trade-off
Both formats offer levels (gzip 1-9, brotli 0-11). Higher level = more CPU spent searching better encodings = smaller file. The curve is harsh:
- Gzip level 6 → 1: file grows only ~5% while compressing 10× faster.
- Brotli level 11 vs 5: saves ~3-6% more and can take 10-50× longer.
The professional strategy: static compression — compress at build time (brotli -11 everything) and serve prepared .br files. Maximum level costs nothing at runtime. Dynamic compression (per request) stays for live-generated content, at medium levels.
What to compress and what not
High compression: HTML, CSS, JS, SVG, JSON, XML, text — typical ratios 70-85%. A 900 KB JS bundle drops to ~250 KB gzipped / ~200 KB brotli.
Don't compress (already compressed internally): JPG, PNG, WebP, AVIF, MP4, WebM, PDF, WOFF2. Re-compressing burns CPU to gain <1% and can even worsen things. Notable: WOFF2 already ships internally brotli-compressed — that's why it weighs so little.
One critical interaction: compression doesn't replace minification, it complements it. Minifying removes bytes (whitespace, comments, long names); compression encodes the rest better. Minified code also compresses better because repeated symbols concentrate. Correct pipeline: minify → compress. Our code minifier covers step one locally.
Practical configuration
Nginx (native gzip; brotli needs the ngx_brotli module):
gzip on;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 6;
# With ngx_brotli:
brotli on;
brotli_comp_level 5;
brotli_types text/plain text/css application/json application/javascript image/svg+xml;
If you serve from Vercel/Netlify/Cloudflare this comes done and well-tuned — one more reason for the modern edge. On your own servers, verify after configuring:
curl -sI -H "Accept-Encoding: br,gzip" https://mydomain.com/ | grep -i content-encoding
No content-encoding back? Something's disabled or the MIME type isn't listed — classic error: serving .svg without declaring image/svg+xml in gzip_types.
FAQ
Compress dynamic responses? Yes for API JSON/text, at medium level and watching generated latency. For rendered HTML evaluate: if TTFB is already tight, on-the-fly compression may hurt more than help — cache first.
Does Brotli work only over HTTPS? Browsers only announce br on secure connections. Plain HTTP gets gzip. In 2026 every serious site runs HTTPS anyway.
How much does zstd improve over brotli? Similar ratios (±3%); zstd clearly wins compression speed. For pre-compressed statics the difference is irrelevant; for massive on-the-fly compression, zstd is the future.
Minify your HTML, CSS and JS before compressing with our online minifier, free and right in your browser.