Skip to content

Split View: Content-Encoding: zstd, Safari 26.3으로 3대 엔진이 채워졌다 — 그리고 다음 단계인 RFC 9842 사전 압축

|

Content-Encoding: zstd, Safari 26.3으로 3대 엔진이 채워졌다 — 그리고 다음 단계인 RFC 9842 사전 압축

들어가며 — 마지막 칸이 채워졌다

2026년 2월 11일에 나온 Safari 26.3의 릴리스 노트에는 이런 한 줄이 있습니다. 원문 그대로 옮기면 — "Added support for Zstandard content encoding in Safari 26.3 for iOS, iPadOS, visionOS, and macOS 26.3." (Apple 릴리스 노트)

이 한 줄로 상황이 바뀝니다. Content-Encoding: zstd는 Chrome 123(2024년 3월), Firefox 126(2024년 5월)이 먼저 지원했고, 남은 큰 조각이 Safari였습니다. 이제 3대 브라우저 엔진(Blink, Gecko, WebKit)이 전부 zstd로 압축된 HTTP 응답을 받습니다. HTTP 전송 압축의 표준 선택지가 gzip, brotli에 이어 셋으로 늘어난 게 실질적으로 완성된 셈입니다.

물론 캐비앳부터 적어 두겠습니다. WebKit 블로그에 따르면 Safari 26.3의 zstd는 iOS/iPadOS/visionOS 26.3과 macOS Tahoe 26.3에서 동작하고, 구형 macOS 위에서 도는 Safari 26.3에서는 동작하지 않습니다(시스템 네트워킹 스택 의존). 그래서 caniuse 집계도 Safari 26.3을 "macOS 26.3 이상 필요"라는 주석과 함께 부분 지원으로 분류합니다 — 글 작성 시점 조회 기준으로 전면 지원 브라우저 점유율 약 79.5%, 부분 지원 약 2.1%입니다.

그래도 도입 리스크 자체는 낮습니다. 압축은 Accept-Encoding 협상으로 결정되므로, zstd를 못 받는 클라이언트에는 brotli나 gzip으로 자동 폴백됩니다. 켠다고 깨질 곳은 없고, 안 켜면 이득도 없습니다.

지원 매트릭스 — zstd와 사전 압축은 상황이 다르다

MDN 브라우저 호환성 데이터 기준으로 정리하면 이렇습니다.

기능ChromeFirefoxSafari
Content-Encoding: zstd123 (2024-03)126 (2024-05)26.3 (2026-02)
dcb / dcz (사전 압축, RFC 9842)130 (2024-10)미지원미지원

윗줄이 이번에 완성된 이야기고, 아랫줄이 이 글 후반부의 이야기입니다. 사전 압축 전송은 2025년 9월에 RFC 9842로 발행됐지만(저자는 Google의 P. Meenan, Shopify의 Y. Weiss) 아직 Chromium 계열만 출하했습니다. 다만 Mozilla는 공식 입장 positive, WebKit은 support를 밝혀 둔 상태라, 반대 때문에 막혀 있는 것은 아닙니다.

zstd가 실제로 사 주는 것 — 숫자는 조건과 함께

zstd 프로젝트가 README에 공개한 자체 벤치마크를 조건 그대로 옮깁니다. Core i7-9700K @ 4.9GHz, Ubuntu 24.04, gcc 14.2.0, lzbench(인메모리), Silesia 코퍼스 — 즉 프로젝트 자체 측정이고, 디스크·네트워크 없이 순수 CPU 처리량을 잰 수치입니다.

압축기 (레벨 1 계열)압축률압축 속도해제 속도
zstd 1.5.7 -12.896510 MB/s1550 MB/s
brotli 1.1.0 -12.883290 MB/s425 MB/s
zlib 1.3.1 -12.743105 MB/s390 MB/s

이 표에서 읽을 것은 순위가 아니라 격차의 성격입니다. 같은 "빠른 레벨" 대역에서 zstd는 zlib보다 압축이 약 5배, 해제가 약 4배 빠르면서 압축률은 오히려 높습니다. 그래서 zstd의 단골 사용처는 동적 응답의 on-the-fly 압축입니다 — WebKit 블로그도 정확히 이 프레임으로 소개합니다. brotli는 보통 빌드 타임에 정적 자산을 최고 레벨로 미리 압축해 두는 쪽에 쓰고, 실시간으로 압축해야 하는 API 응답·HTML에는 zstd가 CPU 예산 대비 유리하다는 것입니다. 참고로 이 표는 레벨 1 기준이라 "brotli 최고 레벨 대비 압축률" 같은 주장은 여기서 도출되지 않습니다 — 알고리즘 내부(LZ77, ANS, Huffman)가 궁금하면 압축 알고리즘 깊이 파기 편을 보세요.

HTTP 밖에서는 이미 검증이 끝난 교체입니다. Discord는 게이트웨이(WebSocket) 압축을 zlib 스트리밍에서 zstd 스트리밍으로 바꾸면서, MESSAGE_CREATE 페이로드의 압축률이 6에서 10 근처로 오르고 평균 크기가 270바이트에서 166바이트로 줄었다고 자체 측정치를 공개했습니다(2024년 롤아웃). 그들이 말하는 "게이트웨이 대역폭 약 40% 절감"은 zstd 단독이 아니라 별도의 passive sessions 최적화와 합산된 수치라는 점은 원문에 명시돼 있습니다.

라이브러리 자체의 릴리스 상태도 짚어 두면 — 최신 릴리스는 v1.5.7(2025-02-19)이고, dev 브랜치 CHANGELOG에는 v1.6.0 항목(레거시 포맷 지원 기본 비활성화)이 준비돼 있지만 2026년 7월 17일 현재 태그도 GitHub 릴리스도 없습니다.

서버 쪽은 아직 비대칭이다

브라우저가 다 받는다고 끝이 아닙니다. 보내는 쪽 지형은 이렇습니다(2026-07 기준, 각 프로젝트 공식 문서·체인지로그 확인).

  • nginx — 공식 zstd 모듈이 없습니다. 공식 문서 모듈 목록에 zstd가 등장하지 않고, 쓰려면 서드파티 모듈을 직접 빌드해야 합니다.
  • Apache httpd — 2.4.x CHANGES에 zstd 항목이 없습니다. 공식은 mod_brotli까지입니다.
  • Caddyencode 디렉티브가 zstd를 지원합니다.
  • Node.js — v23.8.0(2025-02-13)부터 node:zlibzstd 압축·해제 API가 들어갔습니다. 앱 레벨에서 직접 처리할 수 있습니다.
  • CDN — Cloudflare는 전 플랜에서 Zstandard를 지원하고, Free 플랜은 기본 압축이 Zstandard입니다(브로틀리·zstd는 응답 최소 50바이트 조건).

요약하면, 오리진이 nginx/httpd라면 오늘 가장 짧은 경로는 CDN에서 켜는 것입니다. 두 주류 웹서버에 공식 모듈이 없다는 사실이 현재 zstd 배포의 실질적 병목이고, "브라우저 지원 완성"과 "서버 생태계 완성" 사이의 시차가 이 주제의 현재 위치입니다.

다음 단계 — RFC 9842 압축 사전 전송

zstd 지원 완성이 1막이라면, 2막은 사전(dictionary) 압축입니다. 아이디어는 단순합니다. 압축 알고리즘은 "이미 본 데이터"를 참조해 다음 데이터를 줄이는데, 브라우저는 이미 많은 걸 갖고 있습니다 — 어제 받은 JS 번들, 같은 사이트의 다른 HTML. 그걸 사전으로 쓰면 사실상 델타만 전송하면 됩니다.

RFC 9842가 정의하는 흐름을 개념 예시로 그리면 이렇습니다.

# 1) 첫 방문 - 서버가 이 응답을 사전으로 등록하라고 알린다
GET /js/app.v133.js
<- 200 OK
<- Content-Encoding: br
<- Use-As-Dictionary: match="/js/app*.js"

# 2) 다음 방문 - 브라우저가 보유한 사전의 해시를 광고한다
GET /js/app.v134.js
-> Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:
-> Accept-Encoding: gzip, br, zstd, dcb, dcz

# 3) 서버는 그 사전으로 압축된 델타만 보낸다
<- 200 OK
<- Content-Encoding: dcb

별도 사전 리소스를 <link rel="compression-dictionary">로 내려 주는 경로도 있습니다. 새 콘텐츠 인코딩은 두 가지로, dcb는 4바이트 매직 넘버와 사전의 SHA-256 해시(32바이트) 뒤에 Shared Brotli 스트림이 오는 포맷이고(클라이언트는 최대 16MB 윈도 해제 의무), dcz는 zstd의 skippable frame으로 설계된 40바이트 헤더 뒤에 사전 적용 zstd 스트림이 오는 포맷입니다 — 클라이언트는 8MB 또는 사전 크기의 1.25배 중 큰 쪽(상한 128MB)까지의 윈도를 지원해야 합니다. 캐시 가능한 응답이라면 Vary: accept-encoding, available-dictionary가 필수입니다 — 사전이 다른 클라이언트에게 잘못 서빙되는 것을 막기 위해서입니다. 세부는 RFC 9842 4·5장에 있습니다.

실측 수치 — 정적 흐름과 동적 흐름

숫자는 두 부류로 나눠 봐야 합니다. 출처와 조건을 붙입니다.

정적 흐름(이전 버전을 사전으로). RFC 공저자 Meenan이 공개한 테스트 모음이 기준점입니다. 저자 자체 측정이고, brotli CLI 레벨 5, "사전 = 같은 리소스의 이전 버전" 조건입니다.

  • YouTube 데스크톱 플레이어 JS(10MB, 비압축): brotli 단독 1.8MB → 두 달 전 버전을 사전으로 쓰면 384KB(78% 감소). 주 단위 릴리스 간격이면 172KB(90% 감소).
  • CNN 헤더 번들(278KB): brotli 단독 90KB → 1년 전 버전을 사전으로 2KB(98% 감소).
  • Wikipedia처럼 텍스트 위주로 이미 잘 압축되는 페이지는 개선 폭이 훨씬 작았다고 같은 문서에 적혀 있습니다.

동적 흐름(경로 전용 커스텀 사전). 여기엔 실배포 수치가 있습니다. Google 검색은 검색결과 페이지 HTML에 공유 brotli 사전을 배포하고 결과를 Chrome 블로그에 공개했습니다(2025-05-14, 벤더 자체 측정). 대표 샘플로 사전을 만들고 하루에도 여러 번 갱신하는 자동 파이프라인을 돌린 조건에서 —

  • 전체 Chrome 사용자 기준 HTML 페이로드 평균 23% 감소. 이 평균에는 사전이 없는 첫 방문 사용자가 포함됩니다.
  • 사전이 적중한 응답만 보면 50%에 가까운 감소.
  • 체감 지표로는 LCP 개선이 전체 1.7%, 고지연 네트워크에서 최대 9%.

마지막 줄이 이 글에서 제일 정직한 숫자라고 생각합니다. 전송 바이트가 수십 퍼센트 줄어도, 세계에서 가장 최적화된 페이지의 LCP는 1.7% 좋아집니다. 압축 절감률과 체감 속도는 등가가 아닙니다 — 특히 대역폭이 병목이 아닌 환경에서는요. 거꾸로 고지연·저대역 환경에서 최대 9%라는 건, 이 기법의 가치가 사용자 분포에 따라 크게 달라진다는 뜻이기도 합니다.

사전이 답이 아닐 때

균형을 위해 실패 사례도 하나. Discord는 위의 zstd 전환 과정에서 사전도 시도했습니다 — 12만 개의 익명화된 메시지로 JSON용·ETF용 사전 두 개를 훈련시켰습니다. 오프라인 평가에서는 소형 페이로드가 636바이트에서 187바이트로 줄어 유망해 보였지만(사전 없이는 466바이트), 프로덕션에서는 결과가 엇갈렸습니다. 2.5MB짜리 READY 페이로드는 사전을 써도 약 600바이트밖에 더 못 줄였고, MESSAGE_CREATE는 오히려 나빠졌습니다. 결론은 사전 포기 — "사전이 주는 소폭의 압축 개선은 게이트웨이와 클라이언트에 더해지는 복잡도에 못 미쳤다"는 게 그들의 정리입니다.

이유는 구조적입니다. Discord의 연결은 스트리밍 압축 컨텍스트가 살아 있어서, 연결이 이어지는 동안 압축기가 이미 "지난 데이터로 학습"을 합니다. 사전이 돕는 구간은 스트림의 첫 몇 KB뿐입니다. zstd 프로젝트 문서도 같은 말을 합니다 — 사전 이득은 작은 데이터의 첫 몇 KB에 집중되고, 만능 사전은 없으며, 데이터 타입마다 따로 훈련해야 합니다.

HTTP 쪽 사전 압축(RFC 9842)에도 고유 비용이 있습니다.

  • 운영 파이프라인. Google 사례의 전제는 하루 여러 번 사전을 재생성·재배포하는 자동화입니다. 콘텐츠가 바뀌는데 사전이 낡으면 이득이 줄어듭니다. 빌드·서빙 경로에 사전 버저닝이 추가됩니다.
  • 프라이버시 규정. 사전 해시가 추적 쿠키로 악용될 수 있어서, RFC 10장은 클라이언트가 사전을 쿠키와 동일하게 취급하도록 요구합니다 — 쿠키 수준(또는 그 이상)의 파티셔닝, 쿠키 삭제 시 사전도 삭제. 즉 사전 적중률은 쿠키 수명을 넘지 못합니다.
  • 보안 제약. 혼합 소스 압축 공격(CRIME/BREACH 계열, RFC 7457) 고려가 사전에도 그대로 적용됩니다 — 사전에 사용자 데이터를 넣으면 안 되고(Discord도 훈련 데이터를 익명화했습니다), 사전 매칭은 same-origin으로 제한되며 교차 출처는 CORS 통과가 필수입니다.
  • 브라우저 커버리지. 다시, 현재는 Chromium 계열뿐입니다. Firefox/Safari 사용자용 일반 압축 경로는 계속 유지해야 하므로, 사전 압축은 "추가 파이프라인"이지 대체가 아닙니다.

실무 판단

지금 켤 것 — Content-Encoding: zstd. 협상 기반 폴백 덕에 리스크가 낮고, 브라우저 커버리지는 이번 Safari 26.3으로 사실상 채워졌습니다. CDN 뒤에 있다면 설정 스위치 수준입니다. 오리진에서 직접 하려면 nginx/httpd 공식 모듈 부재가 현실적 장애물이니, Caddy·Node 앱 레벨·CDN 중 자기 스택에 맞는 경로를 고르면 됩니다. 동적 응답(on-the-fly)이 핵심 사용처고, 정적 자산 프리컴프레스는 기존 brotli 파이프라인을 유지해도 됩니다.

계산해 보고 결정할 것 — dcb/dcz. 재방문 비중이 높고 JS 번들이 크며 릴리스가 잦은 서비스라면, Chromium 점유율만큼의 절감으로도 계산이 설 수 있습니다. 위 실측처럼 델타 전송은 자릿수가 다른 절감을 주니까요. 단, 사전 생성·버저닝 파이프라인과 이중 서빙 경로를 감당할 팀이어야 하고, 절감 바이트가 아니라 자기 서비스의 체감 지표(LCP 등)로 이득을 검증해야 합니다 — Google조차 전체 LCP 개선은 1.7%였습니다.

하지 말 것. 트래픽이 작거나 사용자 재방문이 드문 사이트가 사전 인프라부터 짓는 것. 스트리밍 압축 컨텍스트가 이미 있는 커스텀 프로토콜에 사전을 얹는 것(Discord의 교훈). 그리고 벤더 절감률을 자기 서비스의 성능 개선으로 등치하는 것.

마치며

정리하면 이렇습니다. Safari 26.3(2026-02-11)으로 Content-Encoding: zstd의 브라우저 퍼즐은 채워졌고, 남은 비대칭은 서버 생태계(두 주류 웹서버의 공식 모듈 부재)입니다. 그 다음 단계인 RFC 9842 사전 압축은 표준화(2025-09)와 실배포 검증(Google 검색, 벤더 자체 측정 평균 23% 페이로드 감소)까지 끝났지만, 브라우저는 아직 Chromium뿐이고 운영 비용과 프라이버시 규정이 실재합니다. zstd는 오늘 켜고, 사전은 자기 트래픽으로 계산부터 하세요. 압축률은 수단이고, 지표는 언제나 사용자 쪽에 있습니다.

참고 자료

Content-Encoding: zstd — Safari 26.3 Fills In the Third Engine, and What's Next Is RFC 9842 Dictionary Compression

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:

FeatureChromeFirefoxSafari
Content-Encoding: zstd123 (2024-03)126 (2024-05)26.3 (2026-02)
dcb / dcz (dictionary compression, RFC 9842)130 (2024-10)Not supportedNot 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)RatioCompression speedDecompression speed
zstd 1.5.7 -12.896510 MB/s1550 MB/s
brotli 1.1.0 -12.883290 MB/s425 MB/s
zlib 1.3.1 -12.743105 MB/s390 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 encode directive supports zstd.
  • Node.js — as of v23.8.0 (2025-02-13), node:zlib gained 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