Every response a site sends costs twice. Once as bytes across a network somebody else is paying for, and once as work on a device turning those bytes back into a page. Compression trades a little of the second for a lot of the first, and on a slow connection that trade is close to free.
Why compress at all
Networks are the slowest part of the stack by a wide margin. A current laptop parses a megabyte of HTML in a few milliseconds. Getting that megabyte to the phone over a congested mobile connection takes seconds. Every byte removed before transmission is time handed back, and it is handed back exactly where the delay actually lives.
The saving is not even linear. A fresh connection does not get the full available bandwidth immediately; it starts cautiously and ramps up, so the first few kilobytes of a response travel in a smaller window than everything after them. Squeezing a page from 181 KB to 19 KB can mean it arrives in meaningfully fewer round trips, not merely in a tenth of the transfer time. On a high-latency link, round trips are the currency that matters.
Then there is what the web is actually made of. HTML, CSS and JavaScript are text, and text is extraordinarily repetitive: the same tags, the same class names, the same keywords, the same identifiers, over and over. That repetition is precisely what a compressor feeds on. Text routinely shrinks to a quarter of its original size or less, which is a bigger win than almost any other single change available.
The benefit lands in three places at once. Visitors see something sooner, especially on mobile and metered connections where the difference is felt rather than measured. Whoever pays for egress ships a fraction of the bytes, and bandwidth is billed by the byte. And the whole thing stays invisible: no visitor notices it, no application code accommodates it, nobody designs around it.
For a performance measure that is an unusual profile. Most of them buy their gains against maintainability, or against how something looks, or against engineering time. This one asks for a checkbox.
Which leaves one question: how do the two ends agree on a format?
The negotiation nobody sees
Every request a browser makes carries a line like this:
Accept-Encoding: gzip, deflate, br, zstdThat is the browser listing the formats it knows how to unpack. The server picks one, compresses, and says which it chose:
Content-Encoding: zstdThe browser decompresses before anything else touches the bytes. Nothing in the page, nothing in JavaScript, and nothing in application code has to know it happened. If a client offers nothing usable, the response goes out uncompressed and still works.
One detail worth knowing: browsers only offer Brotli and Zstandard over HTTPS. Over an unencrypted connection, gzip is what is left.
That last part is the quiet genius of the design. Compression could be added to the web, repeatedly, without ever breaking a client that had not heard of the new format. It is also why adding a fourth format later will be equally undramatic.
The three formats
Three formats matter on the web today, and they arrived roughly a decade apart.
| Format | Year | From | The idea | Strongest at |
|---|---|---|---|---|
| gzip | 1990s | GNU project | The original, available everywhere | Being universally safe |
| Brotli | 2015 | Knows what web code looks like already | Producing the smallest files | |
| Zstandard | 2016 | Facebook, now Meta | Built around size and time together | Unpacking fastest |
Brotli's edge comes from a built-in dictionary: around 13,000 strings that turn up constantly on the web, from HTML tags to CSS properties to common English words. gzip has to describe those patterns from scratch in every single file. Brotli already knows them, which is why it wins the size column below.
Zstandard was built to a different brief. Instead of chasing the smallest possible output, it optimises the trade-off itself: in whatever time is available, produce the smallest file achievable in that time, and unpack again quickly at the other end.
What compresses, and what does not
The redundancy of text draws a clean dividing line. Text formats shrink dramatically: HTML, CSS, JavaScript, JSON, XML, SVG. Already-compressed formats do not shrink at all.
JPEG, WebP, AVIF, MP4, woff2 fonts and zip archives have had their redundancy squeezed out already. Running them through gzip or zstd burns CPU on both ends to achieve approximately nothing, and occasionally makes them slightly larger, since the output still needs a header and the entropy coder has nothing left to work with.
An example from production: scale.sc runs on smoxy. These are not lab figures. Each number is the bytes actually delivered over the wire, at smoxy's own settings, measured by asking for one encoding at a time.
| Content | Uncompressed | gzip | Brotli | Zstandard |
|---|---|---|---|---|
| Homepage HTML | 180.9 KB | 26.2 KB (86%) | 18.6 KB (90%) | 19.2 KB (89%) |
| CSS bundle | 72.9 KB | 17.7 KB (76%) | 13.5 KB (82%) | 13.9 KB (81%) |
| JavaScript bundle | 22.9 KB | 10.9 KB (52%) | 9.5 KB (59%) | 9.8 KB (57%) |
| SVG graphic | 829 B | 488 B (41%) | 445 B (46%) | 487 B (41%) |
| JPEG image | 70.1 KB | not compressed | not compressed | not compressed |
| Unpacking speed (measured separately) | 1.0x | 1.3x | 1.7x |
Percentages are bytes saved against the uncompressed response. The last row is the other half of the story, and the reason the first five do not settle the question on their own. It comes from a separate measurement described further down, not from these files.
Three things stand out. The document the browser needs first, before it can render anything at all, goes from 181 KB to under 19 KB. The SVG, which most people file mentally under "image", loses nearly half its weight, because an SVG is not an image in this sense at all. It is text.
And the JPEG is not compressed. Not "compressed with little effect": the response comes back with no Content-Encoding header whatsoever, whichever format is offered. Spending CPU on both ends to move a JPEG by nothing is not a trade worth making, so it is not made.
That decision is made on content type, and more precisely than a glance at the file extension would suggest. The SVG above is served as image/svg+xml, which makes it formally an image, and it is compressed anyway. What matters is not the image/ prefix but whether the specific type is one that compresses.
This is why image bytes are a different discipline entirely. Shrinking those is a matter of format conversion and quality tuning rather than transport encoding, which is what Optimization is for.
The gaps between the three are worth reading closely. Zstandard lands within about three percent of Brotli on the larger text assets. gzip ships 15 to 41 percent more bytes than Brotli on the same files, which is the cost of a format designed before the web looked like this.
So Brotli wins every size row, by a margin of roughly three percent. Zstandard unpacks about a third faster. Which of those two numbers matters more is the subject of the next section, and it is not the one most comparison tables bother to print.
The part that gets forgotten: decompression
Compression ratio is the number everyone quotes, and it is only half the story. Every compressed response has to be decompressed, once per visitor, on their device, before the page can render.
On a current laptop this is irrelevant. On a mid-range Android phone on a congested network, it is not. Decompression happens on the critical path: the browser cannot parse HTML it has not yet unpacked. It costs time, and it costs battery, and unlike compression it is paid by the visitor rather than by infrastructure.
This is the axis Zstandard was designed around, and it is why the negotiation order above reaches for it first.
Measuring it honestly takes some care. Unpacking a single 20 KB file is over so quickly that the measurement is mostly bookkeeping around the call rather than the work inside it, and all three formats look identical. Run 67 MB of real built pages through instead, repeatedly and with the formats interleaved so neither gets the benefit of a quiet moment, and they separate cleanly: Zstandard unpacks about 1.3 times faster than Brotli and about 1.7 times faster than gzip.
Those are ratios rather than absolute numbers on purpose. Throughput in megabytes per second depends on the processor, the implementation and the payload, and a figure from a server says little about a mid-range phone. The ordering is the durable part, and it held in every run.
The trade is therefore not close. Choosing Brotli buys roughly three percent fewer bytes. Choosing Zstandard buys about a third off the unpacking, on every response, on every device, paid out of the visitor's battery rather than out of infrastructure. Three percent of a 19 KB page is 600 bytes.
How smoxy handles it
Compression happens at the edge, not at the origin. smoxy requests responses from your origin uncompressed, then compresses on the way out. Nothing needs configuring on the origin: no gzip module to tune, no build pipeline to change, no risk of double-compression.
That has a consequence origin-side compression does not: the origin only ever produces one version of a response, the uncompressed one. The edge has to serve every visitor from it, the gzip-only client as readily as the Zstandard-capable one, out of the same cached page.
For every response, the best format the visitor's browser accepts is chosen automatically, in this order:
| Format | Availability | Browser support |
|---|---|---|
| Zstandard (zstd) | Toggle, enabled by default on new zones | Current Chrome, Edge and Firefox |
| Brotli (br) | Always active | Every current browser, Safari included |
| gzip | Always active | Universal fallback |
Brotli and gzip are always on and cannot be disabled, so there is no configuration in which a visitor ends up with an uncompressed page because something is switched off. Zstandard is the one dial, and a browser that has never heard of it simply negotiates Brotli instead, exactly as the design intends.
That mainly means Safari, which does not support Zstandard yet. Nothing changes for visitors on iPhone and Mac: they keep getting Brotli, the smallest of the three.
Newly created zones now start with Zstandard enabled. It is the better default: visitors on browsers that support it get faster decompression at comparable size, and everyone else is unaffected.
Turning it on for an existing zone
Existing zones keep their current setting, so nothing changed underneath anyone. To enable it, open the zone's Proxy page and switch on the toggle in the Compression card. The setting applies zone-wide and takes effect immediately. No cache clear, no redeploy, no origin change.
See Compression for the details.
