필사 모드: Content-Encoding: zstd — Safari 26.3 Fills In the Third Engine, and What's Next Is RFC 9842 Dictionary Compression
English- Introduction — The Last Tile Falls Into Place
- Support Matrix — zstd and Dictionary Compression Are in Different Places
- What zstd Actually Buys You — Numbers With Their Conditions
- The Server Side Is Still Asymmetric
- What's Next — RFC 9842 Compression Dictionary Transport
- Measured Numbers — Static and Dynamic Flows
- When a Dictionary Isn't the Answer
- The Practical Call
- Closing
- References
Introduction — The Last Tile Falls Into Place
Safari 26.3's release notes, out on February 11, 2026, contain this one line. Quoted verbatim: "Added support for Zstandard content encoding in Safari 26.3 for iOS, iPadOS, visionOS, and macOS 26.3." (Apple release notes)
That one line changes the picture. Content-Encoding: zstd was already supported by Chrome 123 (March 2024) and Firefox 126 (May 2024); the one big piece missing was Safari. Now all three browser engines (Blink, Gecko, WebKit) can receive HTTP responses compressed with zstd. The standard menu of HTTP transfer compression options — gzip, brotli, and now zstd — is effectively complete at three.
Let's get the caveats out of the way first. Per the WebKit blog, Safari 26.3's zstd support works on iOS/iPadOS/visionOS 26.3 and macOS Tahoe 26.3, but not on Safari 26.3 running on older macOS versions (it depends on the system networking stack). That's also why caniuse's tally classifies Safari 26.3 as partial support with a "requires macOS 26.3+" note — as of this writing, full support sits at roughly 79.5% of tracked usage and partial support at roughly 2.1%.
Even so, the adoption risk itself is low. Compression is decided by Accept-Encoding negotiation, so clients that can't take zstd fall back automatically to brotli or gzip. Turning it on breaks nothing; leaving it off just means missing out on the upside.
Support Matrix — zstd and Dictionary Compression Are in Different Places
Per MDN's browser-compatibility data:
| Feature | Chrome | Firefox | Safari |
|---|---|---|---|
Content-Encoding: zstd | 123 (2024-03) | 126 (2024-05) | 26.3 (2026-02) |
dcb / dcz (dictionary compression, RFC 9842) | 130 (2024-10) | Not supported | Not supported |
The top row is the story that just got completed; the bottom row is what the second half of this post is about. Compression dictionary transport was published as RFC 9842 in September 2025 (authored by Google's P. Meenan and Shopify's Y. Weiss), but so far only Chromium-family browsers ship it. That said, Mozilla has stated a positive standards position and WebKit has stated support, so it isn't blocked by opposition.
What zstd Actually Buys You — Numbers With Their Conditions
Carrying over the self-reported benchmark the zstd project publishes in its README, conditions intact: Core i7-9700K @ 4.9GHz, Ubuntu 24.04, gcc 14.2.0, lzbench (in-memory), Silesia corpus — i.e., this is the project's own measurement, pure CPU throughput with no disk or network involved.
| Compressor (level-1 tier) | Ratio | Compression speed | Decompression speed |
|---|---|---|---|
| zstd 1.5.7 -1 | 2.896 | 510 MB/s | 1550 MB/s |
| brotli 1.1.0 -1 | 2.883 | 290 MB/s | 425 MB/s |
| zlib 1.3.1 -1 | 2.743 | 105 MB/s | 390 MB/s |
What matters in this table isn't the ranking, it's the shape of the gap. Within the same "fast level" tier, zstd compresses roughly 5x faster and decompresses roughly 4x faster than zlib, while getting a higher ratio besides. That's why zstd's go-to job is on-the-fly compression of dynamic responses — the WebKit blog frames it exactly that way. Brotli is typically used to pre-compress static assets at build time at its highest level, while zstd wins on CPU budget for API responses and HTML that have to be compressed in real time. Worth noting: this table is level-1 only, so no claim like "ratio versus brotli's maximum level" follows from it — if you want the internals (LZ77, ANS, Huffman), see Compression Algorithms Deep Dive.
Outside HTTP, this swap is already a proven one. Discord switched its gateway (WebSocket) compression from zlib streaming to zstd streaming and published its own measurements: the compression ratio for MESSAGE_CREATE payloads rose from around 6 to around 10, and average size dropped from 270 bytes to 166 bytes (rolled out in 2024). Their reported "roughly 40% reduction in gateway bandwidth" figure, the original post states explicitly, isn't zstd alone — it's combined with a separate passive-sessions optimization.
Worth noting the library's own release status too: the latest release is v1.5.7 (2025-02-19), and the dev-branch CHANGELOG has a v1.6.0 entry queued up (legacy format support disabled by default), but as of July 17, 2026 there's neither a tag nor a GitHub release for it.
The Server Side Is Still Asymmetric
Browsers receiving it isn't the whole story. Here's the sending side (as of 2026-07, confirmed against each project's own docs and changelog):
- nginx — no official zstd module. zstd doesn't appear in the official module list; using it means building a third-party module yourself.
- Apache httpd — no zstd entry in the 2.4.x CHANGES. Official support tops out at mod_brotli.
- Caddy — the
encodedirective supports zstd. - Node.js — as of v23.8.0 (2025-02-13),
node:zlibgained zstd compression/decompression APIs. You can handle it directly at the app level. - CDN — Cloudflare supports Zstandard on every plan, and on the Free plan Zstandard is the default compression (brotli and zstd both require a minimum 50-byte response).
In short, if your origin is nginx or httpd, the shortest path today is turning it on at the CDN. The absence of an official module in the two mainstream web servers is the real bottleneck in zstd's rollout right now, and the lag between "browser support is done" and "server ecosystem is done" is exactly where this topic currently sits.
What's Next — RFC 9842 Compression Dictionary Transport
If completing zstd support is act one, act two is dictionary compression. The idea is simple. A compression algorithm shrinks the next chunk of data by referencing data it has already seen — and browsers already have plenty on hand: yesterday's JS bundle, other HTML from the same site. Use that as a dictionary and, in effect, you only have to send the delta.
Sketched as a conceptual example, the flow RFC 9842 defines looks like this:
# 1) First visit - the server tells the browser to register this response as a dictionary
GET /js/app.v133.js
<- 200 OK
<- Content-Encoding: br
<- Use-As-Dictionary: match="/js/app*.js"
# 2) Next visit - the browser advertises the hash of the dictionary it holds
GET /js/app.v134.js
-> Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:
-> Accept-Encoding: gzip, br, zstd, dcb, dcz
# 3) The server sends only the delta, compressed against that dictionary
<- 200 OK
<- Content-Encoding: dcb
There's also a path for serving a separate dictionary resource via <link rel="compression-dictionary">. The two new content encodings are: dcb, a format with a 4-byte magic number and the dictionary's SHA-256 hash (32 bytes) followed by a Shared Brotli stream (clients must be able to decompress up to a 16MB window); and dcz, a format designed as a zstd skippable frame — a 40-byte header followed by a dictionary-applied zstd stream — where clients must support a window up to the larger of 8MB or 1.25x the dictionary size (capped at 128MB). For cacheable responses, Vary: accept-encoding, available-dictionary is required, to keep a dictionary from being served incorrectly to a different client. The details are in RFC 9842, sections 4 and 5.
Measured Numbers — Static and Dynamic Flows
The numbers split into two buckets. I'll attach sources and conditions to each.
Static flow (previous version as the dictionary). The baseline is the test collection published by RFC co-author Meenan. It's the author's own measurement, using brotli CLI level 5, with "dictionary = a previous version of the same resource."
- YouTube desktop player JS (10MB, uncompressed): brotli alone gets it to 1.8MB → using a two-month-old version as the dictionary gets it to 384KB (78% reduction). At a weekly release cadence, 172KB (90% reduction).
- CNN header bundle (278KB): brotli alone gets it to 90KB → using a year-old version as the dictionary, 2KB (98% reduction).
- The same document notes that a text-heavy page that's already well compressed, like Wikipedia, saw a much smaller improvement.
Dynamic flow (a custom, path-specific dictionary). This is where a real-world rollout number exists. Google Search deployed a shared brotli dictionary for its search-results-page HTML and published the results on the Chrome blog (2025-05-14, vendor's own measurement). Under an automated pipeline that builds the dictionary from a representative sample and refreshes it multiple times a day —
- HTML payload down 23% on average across all Chrome users. That average includes first-time visitors who have no dictionary yet.
- Looking only at responses where the dictionary hit, the reduction is closer to 50%.
- On the user-perceived metric, LCP improved 1.7% overall, and up to 9% on high-latency networks.
That last line is, I think, the most honest number in this whole post. Even when transferred bytes drop by tens of percent, LCP on one of the most heavily optimized pages in the world improves by 1.7%. Compression savings and perceived speed are not the same thing — especially in environments where bandwidth isn't the bottleneck. Conversely, the "up to 9%" on high-latency, low-bandwidth networks also means this technique's value swings a lot with your user distribution.
When a Dictionary Isn't the Answer
For balance, one failure case too. During the zstd migration above, Discord also tried dictionaries — training two dictionaries, one for JSON and one for ETF, on 120,000 anonymized messages. In offline evaluation it looked promising, shrinking small payloads from 636 bytes to 187 bytes (466 bytes without the dictionary), but in production the results were mixed. The 2.5MB READY payload only shrank by about 600 bytes more even with the dictionary, and MESSAGE_CREATE actually got worse. The conclusion was to drop the dictionary — their summary was that "the modest compression gain a dictionary provided wasn't worth the added complexity on the gateway and clients."
The reason is structural. Discord's connections keep a streaming compression context alive, so for the life of the connection the compressor is already "learning from past data" on its own. A dictionary only helps for the first few KB of a stream. The zstd project's own documentation says the same thing — dictionary gains concentrate in the first few KB of small pieces of data, there's no universal dictionary, and you have to train one separately per data type.
HTTP-side dictionary compression (RFC 9842) carries its own costs too.
- Operational pipeline. Google's case presupposes automation that regenerates and redeploys the dictionary multiple times a day. If content changes and the dictionary goes stale, the gain shrinks. Dictionary versioning gets added to your build and serving path.
- Privacy rules. Because a dictionary hash could be abused as a tracking cookie, RFC section 10 requires clients to treat a dictionary the same as a cookie — cookie-level (or stricter) partitioning, and deleting the dictionary whenever cookies are cleared. In other words, dictionary hit rate can never outlive cookie lifetime.
- Security constraints. Mixed-source compression attacks (the CRIME/BREACH family, RFC 7457) apply here too — a dictionary must not contain user data (Discord anonymized its training data for exactly this reason), dictionary matching is restricted to same-origin, and cross-origin use requires passing CORS.
- Browser coverage. Again, right now it's Chromium-family only. You still have to maintain the general compression path for Firefox/Safari users, so dictionary compression is an "additional pipeline," not a replacement.
The Practical Call
Turn this on now — Content-Encoding: zstd. Negotiation-based fallback keeps the risk low, and browser coverage is effectively complete now with Safari 26.3. If you're behind a CDN, it's a config toggle. If you're doing it at the origin, the lack of an official nginx/httpd module is the real obstacle, so pick whichever path fits your stack — Caddy, app-level Node, or a CDN. Dynamic (on-the-fly) responses are the core use case; you can keep your existing brotli pipeline for static-asset precompression.
Run the numbers before deciding — dcb/dcz. If your service has a high share of returning visitors, large JS bundles, and frequent releases, the math can work out even with just Chromium's share of traffic — as the measurements above show, delta transfer gives you savings an order of magnitude different. But you need a team that can carry a dictionary-generation-and-versioning pipeline plus a dual serving path, and you have to validate the gain against your own service's felt metrics (LCP, etc.), not bytes saved — even Google's overall LCP improvement was 1.7%.
Don't. Don't build dictionary infrastructure first if your traffic is small or repeat visits are rare. Don't bolt a dictionary onto a custom protocol that already has a live streaming compression context (Discord's lesson). And don't equate a vendor's reported savings with a performance win on your own service.
Closing
To sum up: with Safari 26.3 (2026-02-11), the browser side of the Content-Encoding: zstd puzzle is complete, and what's left asymmetric is the server ecosystem (the lack of an official module in the two mainstream web servers). The next step, RFC 9842 dictionary compression, has cleared both standardization (2025-09) and real-world validation (Google Search, a vendor-measured 23% average payload reduction) — but browser support is still Chromium-only, and the operational cost and privacy rules are real. Turn zstd on today; run the numbers on your own traffic before touching dictionaries. Compression ratio is a means; the metric that matters always sits on the user's side.
References
- Safari 26.3 Release Notes — Apple Developer
- WebKit Features in Safari 26.3 — webkit.org
- RFC 9842 — Compression Dictionary Transport
- Content-Encoding — MDN (browser compatibility table)
- zstd (Zstandard) content-encoding — caniuse
- facebook/zstd — Benchmarks (the project's own measurement)
- Compression dictionary transport examples — WICG (author's own measurement)
- How Google Search made their results pages 23% smaller — Chrome for Developers (vendor's own measurement)
- How Discord Reduced Websocket Traffic by 40% — Discord (vendor's own measurement)
- Zstd Content-Encoding — Chrome Platform Status
- Compression dictionary transport — Chrome Platform Status
- Mozilla standards-positions #771 (positive) · WebKit standards-positions #160 (support)
- Node.js v23.8.0 — node:zlib zstd support
- Cloudflare compression docs · Caddy encode directive
- Compression Algorithms Deep Dive — LZ77, zstd, brotli, ANS, Huffman (related post)
현재 단락 (1/74)
Safari 26.3's release notes, out on February 11, 2026, contain this one line. Quoted verbatim: "Adde...