Compression
Date: 2026-08-17
Shrinking responses in transit. It’s close to free, it’s frequently misconfigured in ways nobody notices, and it does nothing for the assets that dominate most page weight — because images, video and fonts are already compressed and re-compressing them wastes CPU for nothing.
HTTP compression is encoding a response body — typically with gzip or Brotli — so fewer bytes cross the network, negotiated by the Accept-Encoding and Content-Encoding headers.
What compresses, and what doesn’t
TEXT — compresses enormously ALREADY COMPRESSED — leave alone
HTML ~75–85% reduction JPEG, PNG, WebP, AVIF
CSS ~75–85% MP4, WebM
JavaScript ~65–80% WOFF2 ← already Brotli internally
JSON ~80–90% ZIP, PDF
SVG ~70–80%
Compressing an already-compressed file typically makes it very slightly larger and costs CPU on both ends. Server configs that compress image/* are a real and common misconfiguration — they burn origin CPU to add bytes.
gzip and Brotli
gzip Brotli
support universal universal in modern browsers
compression ratio baseline ~15–20% better on text
compress speed fast slower at high levels
decompress speed fast comparable
best used dynamic static assets, precompressed
responses
The key operational distinction is compression level, not algorithm. Brotli’s highest levels are far too slow to run per-request on dynamic HTML, and the same setting is ideal for a static file compressed once at build time.
STATIC ASSETS DYNAMIC RESPONSES
JS, CSS, fonts, SVG HTML, API JSON
compress ONCE at build time compress PER REQUEST
Brotli level 11 (maximum) Brotli level 4–5, or gzip 6
serve the precompressed file the CPU cost is paid on every hit
CPU cost: irrelevant, it's a CPU cost: real. high levels here
one-off add latency to every response
Precompressing static assets at build time is the single most missed win here. Most build tools emit .br and .gz alongside the original; most servers need explicit configuration to serve them. Without it you’re either paying per-request CPU or shipping uncompressed.
Where it happens
The chain matters, because compression applied twice is wasted and applied zero times is invisible.
origin → CDN → browser
compression can be applied at either the origin or the CDN
✓ origin compresses, CDN passes through
✓ origin sends plain, CDN compresses at the edge
← often better: the CDN caches both variants and
the origin does less work
✗ both compress → the CDN decompresses to recompress,
or double-encodes and breaks
Watch the Vary header. A CDN caching a compressed response and serving it to a client that didn’t ask for compression breaks the page. Vary: Accept-Encoding tells shared caches to key on the encoding, and omitting it is a classic cause of “the site is broken for one user and nobody can reproduce it” — CDN Caching, HTTP Caching.
Checking it works
curl -sI -H "Accept-Encoding: br, gzip" https://example.com/app.js \
| grep -i "content-encoding\|content-length\|vary"
content-encoding: br
content-length: 42184
vary: Accept-Encoding
No content-encoding line means the response isn’t compressed, which on a JavaScript or CSS file is usually a several-hundred-kilobyte oversight.
Things worth auditing specifically, because they’re the ones that slip through:
- API responses. JSON compresses ~85% and is frequently uncompressed because the API was configured separately from the web server
- Responses above a size threshold. Many servers skip compression below ~1 KB, which is correct — the header overhead exceeds the saving
- SVG, which is text and is often served as a binary type that the compression rules skip
- Streamed and chunked responses, where compression is sometimes disabled by default
- Anything behind a proxy you didn’t configure
The security footnote
Compressing a response that mixes secret and attacker-influenced content in the same body can leak the secret through response size — the BREACH class of attack. It’s a real consideration for pages containing CSRF tokens alongside reflected user input, and the mitigations are token masking or excluding such responses from compression rather than turning compression off site-wide. Worth knowing exists; rarely worth acting on for a typical retail site — Common Vulnerabilities.
What it doesn’t fix
Compression reduces transfer, not work. A 400 KB JavaScript bundle compressing to 110 KB still parses, compiles and executes 400 KB of code — and on a mid-range phone that’s the expensive part — JavaScript Execution Cost, Bundle Analysis.
It also does nothing for the assets that usually dominate page weight. On a typical retail page, images are most of the bytes and are already compressed, so the answer there is format and dimensions rather than transport encoding — Image Optimisation, Modern Image Formats.
Where it interacts
- HTTP Caching —
Vary: Accept-Encodingis the correctness requirement that connects the two - CDN Caching — usually the best place to compress, and the place a misconfiguration is hardest to spot
- Time to First Byte — per-request compression at too high a level adds directly to it
- HTTP Versions — HTTP/2 and later compress headers separately, which is a different mechanism to this