Tags: web-dev concept

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-Encoding is 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