The PageSpeed audit recommends Brotli. The team enables brotli on; brotli_comp_level 11 on nginx for everything — including a 200 KB JSON API generated on every request. Time to first byte jumps from 120 ms to 380 ms on a two-vCPU VPS; the Lighthouse score still rises on "bytes transferred". Mobile users feel the slowness.
Compression reduces bytes — it costs CPU and sometimes time to first byte. Brotli vs gzip is not a religion: it is a trade-off on where and at what level to compress, depending on content type and server load.
gzip vs Brotli: the trade-offs
| Algorithm | Compression | Compression speed | Typical use |
|---|---|---|---|
| gzip level 6 | Good | Fast | Dynamic HTML, APIs |
| brotli level 4–6 | Better | Moderate | Dynamic if CPU available |
| brotli level 11 + precompress | Maximum | Offline | Static .js/.css |
Precompression means generating file.js.br upfront (brotli -k file.js) then serving it with Content-Encoding: br — zero runtime CPU.
Compressing a JPEG image means paying CPU for zero bytes saved.
nginx, Apache, CDN: one layer only
On nginx, configure gzip on; gzip_types ...; brotli on; brotli_types ...; with moderate levels (4–6). Cloudflare and similar CDNs often include edge compression — disable double compression at origin.
On shared hosting, compression is sometimes imposed by the control panel. Measure impact on time to first byte before stacking layers.
Dynamic vs static: two strategies
For static assets, the build pipeline produces .gz and .br files; nginx enables gzip_static on; brotli_static on;. For dynamic content, gzip level 5 is often enough — measure p95 TTFB before aggressive Brotli. For JSON APIs, compression helps above 1–2 KB; micro-responses do not benefit.
HTTP/2 and HTTP/3 change header compression, not the logic of compressible bodies.
Measure: bytes vs latency
Compare bytes transferred (browser dev tools), time to first byte (Navigation Timing) and origin CPU under real load. The goal is byte savings without increasing TTFB by more than 50 ms on critical pages.
Common mistakes
Double compression PHP plus nginx. Forgetting gzip_min_length 256. Brotli on SSE or WebSocket streams where it is inappropriate. Maximum level on HTML generated under load, turning a modest VPS into a CPU bottleneck.
The climax: Lighthouse score, degraded UX
Compressing without adding latency means precompressing static assets, moderating dynamic compression, and compressing at the right network hop (edge over origin).
Decide and move forward without blind spots
Audit MIME types served and drop useless compression on already-compressed formats. Precompress static assets at build time and serve via nginx or CDN. Apply moderate gzip or Brotli on dynamic HTML after measurement. Verify only one layer compresses each response. Measure time to first byte before and after any change. Compare hosts and CDNs via the directory and compare tool.
Frequently asked questions
Is Brotli always better than gzip?
Better text ratio, especially at high levels — slower to compress. Precompress static; moderate dynamic.
Where to compress — nginx, CDN or PHP?
Edge or nginx; not double PHP+nginx.
Which MIME types to compress?
Text (html, css, js, json, svg) — not already-compressed images.
How to serve Brotli to older browsers?
Accept-Encoding negotiation: br then gzip fallback; CDN Vary.
Fewer bytes only matter if the first byte arrives on time.