---
title: "Compression (gzip, brotli, zstd)"
category: performance
status: required
url: https://specification.website/spec/performance/compression/
updated: "2026-05-29"
sources:
  - title: "RFC 7932 — Brotli Compressed Data Format"
    url: "https://www.rfc-editor.org/rfc/rfc7932"
    publisher: "IETF"
  - title: "MDN — Accept-Encoding"
    url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding"
    publisher: "MDN"
  - title: "MDN — Content-Encoding"
    url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding"
    publisher: "MDN"
  - title: "RFC 8878 — Zstandard Compression"
    url: "https://www.rfc-editor.org/rfc/rfc8878"
    publisher: "IETF"
source_repo: https://github.com/jdevalk/specification.website
licence: CC-BY-4.0
---

# 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`.

```http
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_brotli` and `mod_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-Encoding` behind 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` — check `Content-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.
