Compression (gzip, brotli, zstd)
Compress text responses with brotli where supported, gzip everywhere else. zstd is emerging. Don't compress already-compressed media.
What it is
HTTP content encoding compresses response bodies on the server and decompresses them in the browser. The client advertises supported algorithms in Accept-Encoding; the server picks one and announces it in Content-Encoding.
Request:
Accept-Encoding: br, gzip, zstd
Response:
Content-Encoding: br
Vary: Accept-Encoding
The common algorithms:
- gzip — universally supported since the late 1990s. Good ratio, fast.
- brotli (br) — Google, standardised as RFC 7932. 15–25% smaller than gzip for text. Supported by all modern browsers.
- zstd — RFC 8878. Comparable ratio to brotli with faster decompression. Supported by Chrome 123+, Firefox 126+, Safari 17.4+.
- deflate — avoid. Ambiguity between raw deflate and zlib wrapping has bitten implementations for decades.
Why it matters
Text resources — HTML, CSS, JavaScript, JSON, SVG, XML — compress to 20–30% of their original size. On a 200KB JavaScript bundle, that is 140KB saved per request, multiplied by every user. Compression is one of the cheapest performance wins available, and a Lighthouse audit failure if missing.
How to implement
Compress all text. HTML, CSS, JS, JSON, SVG, XML, plain text, and most fonts (WOFF2 is already compressed; WOFF is not).
Negotiate via Accept-Encoding. Most servers and CDNs do this automatically. Configure them to prefer brotli, fall back to gzip:
- nginx:
brotli on; brotli_static on; gzip on; - Apache:
mod_brotliandmod_deflate. - Cloudflare, Fastly, Cloudfront: enable in the dashboard.
Pre-compress static assets. For files that don’t change (your bundled JS), compress at build time to maximum level (brotli quality 11, gzip level 9) and let the server serve the .br or .gz file directly. Runtime compression usually runs at level 5–6 for CPU reasons.
Set Vary: Accept-Encoding. Tells CDNs to keep a separate cache entry per encoding. Without it, gzip clients may receive a brotli body they can’t decode.
Don’t double-compress. Images (JPEG, PNG, WebP, AVIF), video, fonts (WOFF2), and zip files are already compressed. Re-encoding wastes CPU and often grows the file.
Common mistakes
- Compression off entirely. Lighthouse “Enable text compression” fails immediately.
- gzip only — a 20% saving left on the table for every brotli-capable browser.
- Compressing images or video. Pure waste.
- Missing
Vary: Accept-Encodingbehind a CDN. Cache poisoning waiting to happen. - Brotli at quality 11 on dynamic responses. The encode cost erases the wire saving.
Verification
curl -H "Accept-Encoding: br, gzip" -I https://example.com/main.css— checkContent-Encoding.- DevTools → Network → response headers should show
content-encoding: br. - Compare Network “Size” (over the wire) vs “Resources” (decompressed). The ratio shows compression is working.
Related topics
Sources & further reading
- RFC 7932 — Brotli Compressed Data Format — IETF
- MDN — Accept-Encoding — MDN
- MDN — Content-Encoding — MDN
- RFC 8878 — Zstandard Compression — IETF