Every byte a server delivers has to travel down the wire to the browser before the first content becomes visible. Text compression is the lever that shrinks exactly this transfer size - and therefore acts directly on Time to First Byte and Largest Contentful Paint. For years the answer was simply Gzip or Brotli. Since 2024, Zstandard (zstd) has arrived as a third option, available as an HTTP content encoding in the major browsers (Can I use). In the HTTP Archive analysis, 37 percent of mobile HTML documents are already compressed with Brotli, while 11 percent send no compression at all (HTTP Archive Web Almanac). This article places Gzip, Brotli and zstd in their proper roles: which one produces the smallest files for static build assets, where zstd shines for dynamic HTML and JSON APIs, how content-encoding negotiation works correctly and how to measure the real byte savings.
Why Text Compression Decides LCP and TTFB
HTML, CSS and JavaScript are text formats with high redundancy - recurring tags, class names, keywords and whitespace. That very redundancy compresses well. A typical HTML response shrinks with modern compression to a quarter or a fifth of its size, and JavaScript bundles often compress even more. Fewer transferred bytes mean fewer roundtrips in TCP slow start and a shorter time until the document is fully received. Because the browser can only discover further resources after the HTML has arrived, a smaller HTML response moves the entire downstream loading chain forward.
The link to user experience is well documented. Google describes Time to First Byte as one of the values that caps Largest Contentful Paint - if the HTML arrives late, even the largest visible element cannot appear early (Google web.dev). And the economic effect is measurable: improving mobile load time by just 0.1 seconds raised the retail conversion rate by up to 8.4 percent (Deloitte). Compression is therefore not a detail for purists but a building block that co-decides Core Web Vitals and revenue.
Compression acts exactly at the latency-critical point
Gzip, Brotli and Zstandard at a Glance
The three relevant methods differ less in goal than in the trade-off between compression ratio and computational cost. Gzip is the oldest and most universal standard: supported by every browser and server, fast, with solid ratios. Brotli was developed by Google and standardized as RFC 7932 (IETF RFC 7932); at a high level it achieves the best text compression ratios of the three. Zstandard originated at Meta, is standardized as RFC 8878 (IETF RFC 8878) and, in the HTTP world, has been usable as the zstd content encoding since 2024. Its hallmark is a very wide range between very fast and very strong compression, tunable via the level.
A look at raw speed makes zstd's character clear. In the Zstandard project's benchmark, zstd at its default level compresses at around 510 MB/s, while the comparable zlib Gzip level reaches only about 105 MB/s - roughly 5 times faster at a slightly better ratio (Zstandard project benchmark). Important for server use: zstd's decompression speed stays consistently high across all levels (Zstandard project benchmark), so a higher compression level does not slow the client down.
Gzip: the universal standard
Understood by every client, fast and resource-friendly. For old browsers, unencrypted connections and as a fallback layer, Gzip remains the reliable base of any compression strategy.
Brotli: smallest text files
At maximum level Brotli produces the smallest files of the three methods for HTML, CSS and JavaScript (IETF RFC 7932). The price is a high compression time that only amortizes with one-time pre-compression.
Zstandard: fast and flexible
With a single library, zstd covers the range from very fast to very strong (IETF RFC 8878). This breadth makes it especially attractive for dynamic responses compressed in real time.
The market data mirrors these roles. For JavaScript, Brotli has now overtaken Gzip and sits at around 45 percent of mobile deliveries versus 41 percent for Gzip; zstd appears at about 1 percent, only at the start of its adoption (HTTP Archive Web Almanac). In practice this means zstd complements the stack, it does not replace Brotli and Gzip. Combining the three methods by role extracts the maximum - a core theme of every server optimization.
Compressing Static Assets: Brotli at Maximum Level
Static build assets - the shipped JavaScript bundle, the CSS file, SVG icons - are generated once during the build and then delivered unchanged millions of times. That is the ideal case for expensive but maximal compression. At Brotli level 11 the smallest file emerges; the fact that compression takes seconds rather than milliseconds is irrelevant, because it happens only once at deployment. The web server then serves the pre-compressed .br file directly, without recomputing per request.
Pre-compress instead of on the fly
.br and a .gz variant next to each file and let the web server serve the matching one. Clients then get the smallest possible file without the server spending CPU time compressing on every request - a standard step in our frontend optimization.# Serve pre-compressed files directly (ngx_brotli + gzip_static)
brotli_static on; # serves an existing .br file
gzip_static on; # fallback: existing .gz file
# Only on the fly where no pre-compression exists (dynamic)
brotli on;
brotli_comp_level 5; # moderate level for real time
brotli_types text/html text/css application/javascript application/json image/svg+xml;
# Keep cache variants separated per encoding
add_header Vary Accept-Encoding always;For static assets the choice is therefore clear: Brotli 11 as the pre-compressed variant, Gzip 9 as the fallback for clients without Brotli. zstd brings little advantage here, because compression time does not matter anyway and Brotli 11 delivers the slightly smaller file. The gain lies in the long cache lifetime: once compressed, the file is served with Cache-Control: immutable for months - an interplay we describe in detail in caching strategies with Varnish and Redis.
Compressing Dynamically: Where zstd Shines
Dynamic responses are a completely different case. Personalized HTML, an API's JSON responses, server-rendered pages with changing content - all of it is created anew per request and must be compressed in real time while the user waits. Here it is not the last compression level that counts but the ratio of saved bytes to spent CPU time. This is exactly the discipline in which zstd plays to its strength: at medium levels it reaches ratios close to Brotli, yet compresses considerably faster.
The order-of-magnitude difference is significant. Brotli at maximum level 11 is too slow for real-time compression; at a high level, zstd compresses comparable content around 4 times faster than Brotli 11, with only a marginally larger file (Zstandard project benchmark). For a JSON API delivering thousands of responses per second, that means noticeably less CPU load per request - compute time that benefits the backend and thus Time to First Byte.
- Pre-compress static build assets with Brotli 11, provide Gzip 9 as a fallback
- Compress dynamic HTML and JSON in real time - zstd at a medium to high level or Brotli 4 to 6
- Set the compression level by CPU load, not by the theoretical maximum ratio
- Exclude already compressed formats (JPEG, WebP, AVIF, ZIP, WOFF2) from compression
- Leave very small responses under about one kilobyte uncompressed - the overhead is not worth it
- Verify encoding choice and compression level against real responses after deployment
Do not re-compress compressed data
Brotli and Zstandard in Direct Comparison
The following comparison summarizes when each method is the better choice. What matters is the split between once-generated static files and per-request dynamic responses - not the question of which algorithm is universally best.
| Criterion | Brotli (level 11) | Zstandard (medium to high) |
|---|---|---|
| Text compression ratio | highest of the three methods | close to Brotli, marginally larger |
| Compression speed | low, seconds per MB | high, about 4x faster than Brotli 11 |
| Decompression | fast | fast and level-independent |
| Ideal use | static build assets | dynamic HTML and JSON |
| Standardized as | IETF RFC 7932 | IETF RFC 8878 |
| Browser content encoding | nearly universal | from Chrome and Edge 123, Firefox 126, Safari 26 |
The browser-support row is the reason zstd is a complement, not a replacement. As an HTTP content encoding, zstd is understood by Chrome and Edge from version 123, by Firefox from 126 and by Safari from 26 (Can I use). Older clients do not know it and could not decompress a zstd response - which is why the server must fall back cleanly to Brotli or Gzip for them. One more detail: browsers send Accept-Encoding: zstd only over HTTPS, not over unencrypted HTTP (MDN Web Docs).
Negotiating Content-Encoding Cleanly
Which method is used is not decided by the server alone but by a negotiation between client and server. In every request the browser announces via the Accept-Encoding header which methods it understands - for example zstd, br, gzip. The server picks the best shared method, compresses the response accordingly and signals the encoding actually used in the response's Content-Encoding header (MDN Web Docs). If the client understands none of the preferred methods, the chain falls back to Gzip and, in the extreme, to uncompressed delivery.
# Browser request (modern version, over HTTPS)
GET /api/products HTTP/2
Accept-Encoding: zstd, br, gzip
# Server response - zstd chosen
HTTP/2 200 OK
Content-Type: application/json
Content-Encoding: zstd
Vary: Accept-EncodingDo not forget Vary: Accept-Encoding
Vary: Accept-Encoding. This header instructs caches to keep a separate variant per encoding. Without it, broken responses threaten a portion of visitors - a classic configuration mistake we check for specifically in our technical analysis.Building the Fallback Chain Correctly
A robust compression strategy in 2026 is a staged chain, not a single decision. It starts with the strongest method the client understands and falls back in an orderly way when a prerequisite is missing. That way every visitor gets the smallest response their browser can process, without excluding any client from delivery.
- Static assets: pre-compressed Brotli 11, otherwise pre-compressed Gzip 9
- Dynamic responses to modern clients over HTTPS: zstd at a medium to high level
- Dynamic responses to clients with Brotli but without zstd: Brotli at level 4 to 6
- Clients without Brotli and zstd, and unencrypted connections: Gzip
- Very old or exotic clients without any compression: uncompressed delivery
The appeal of this chain lies in its asymmetric risk-reward profile: modern browsers get the full speed advantage, older ones lose nothing because the fallback is lossless. This way of thinking - use the most modern technique without excluding anyone - runs through all performance work, from the protocol to resource prioritization. How a similar principle pays off during connection setup is shown in the piece on HTTP/3 and QUIC.
Measuring the Real Byte Savings
Compression is an optimization you should measure rather than guess. The most important value is the actually transferred size per response - visible in the browser's developer tools as transferred size versus decoded size. The ratio of the two immediately shows how strongly a response was compressed and whether it is compressed at all. The Content-Encoding header additionally reveals which method the server actually chose.
For a reliable assessment a single glance is not enough. You compare the methods on the same representative documents - the largest HTML response, the main JavaScript bundle, the API's largest JSON responses. Only this comparison shows whether moving from Gzip to Brotli or introducing zstd for dynamic responses delivers the expected gain and how it affects CPU load and Time to First Byte. In practice the switch pays off most where large text responses meet many users (project experience).
It is not the theoretical maximum ratio that decides, but the smallest response the respective client can process losslessly - measured on real documents, not on synthetic test files.
The Optimal Compression Stack for 2026
The best compression stack in 2026 is not an either-or but a deliberate division of labour. Brotli at maximum level delivers the smallest static files and belongs in the build process. zstd compresses dynamic HTML and JSON quickly and efficiently, relieving the server under load. Gzip remains the universal fallback layer that excludes no one. Above all sits correct negotiation with a clean Vary header, so that caches and CDNs do not mix the variants.
Compression is a building block, not a cure-all. It shrinks the transfer size but changes nothing about a slow database, an overloaded frontend or a bloated DOM. Its full value emerges only in concert with a fast backend, sensible caching and lean markup - such as reducing the DOM size and a well-used back-forward cache. In our performance projects we set up the compression stack as part of server optimization: Brotli for assets, efficient zstd or Gzip for dynamic responses - without driving up server load and with proven byte savings.
Sources and Studies