Classic compression treats every response as a blank slate. Brotli or Zstandard compress the JavaScript bundle against a built-in default dictionary - without knowing that the browser has been holding the previous version of the very same file in cache all along. After every deploy, returning users therefore download the complete bundle again, even though perhaps three percent of the code has changed. Compression Dictionary Transport closes exactly this gap. The mechanism was approved as RFC 9842 on the Standards Track in September 2025 (IETF RFC 9842) and is available in Chrome and Edge from version 130 (Can I use). The browser announces via the Available-Dictionary header which previous version it holds; the server replies with a delta compressed against that dictionary. According to MDN, this can achieve an order of magnitude more compression than compression using a default built-in dictionary (MDN Web Docs). This article walks through both practical patterns, the required server configuration, the cache pitfalls and an honest answer to the question of where the effort pays off.
Key takeaways
- Compression Dictionary Transport became RFC 9842 on the Standards Track in September 2025 and ships in Chrome and Edge from version 130 (Can I use). The algorithm stays Brotli or Zstandard - only the dictionary changes.
- Pattern one uses the previous version of the same bundle as the dictionary: one front-end bundle dropped from 344 KB with Brotli to 128 KB as a delta, a cut of 63 percent (WICG). Shorter gaps between releases produce smaller deltas.
- Pattern two builds a static dictionary for dynamic HTML. Google Search cut average HTML delivery by 23 percent versus plain Brotli and improved LCP by 1.7 percent, and by up to 9 percent on high-latency connections (Chrome for Developers).
- Setup means the Use-As-Dictionary header with a URL pattern, HTTPS as a hard requirement, and Vary: Accept-Encoding, Available-Dictionary on every cacheable response. The SHA-256 hash in each delta response exposes a mismatched dictionary immediately.
- The cost sits in the intermediate cache layer: when users hold different previous versions, each one creates its own cache variant of the same URL. That is why deltas are often computed at the edge or limited to a few previous versions.
- Browsers without support send no dictionary header and receive the ordinary Brotli or Gzip response; global coverage is around 69 percent (Can I use). With small bundles, rare deploys or mostly new visitors, the gain is close to zero.
Why Every Deploy Costs the Entire Bundle Again
Modern front ends ship their JavaScript under hashed file names: app.9f2c1d.js is stored in the browser cache for a year with Cache-Control: immutable. That is correct and fast - as long as nothing gets deployed. Change a single line and the hash changes, the URL is a different one, and the cache entry for the old file simply becomes worthless. The browser downloads the new file in full, even though it already holds most of its content. With weekly deploys, a returning user pays that full price every single week.
The effect scales with bundle size, and in many projects that size keeps growing. The problem rarely shows up in the lab; it shows up in the field, with users on mobile networks, in fringe locations and on older devices, where every additional kilobyte costs noticeable time. Anyone steering a front end through JavaScript performance budgets knows the mechanics: the transfer size of the main bundle caps how early the page becomes interactive - and therefore how the Core Web Vitals turn out in the field.
The returning user is the overlooked case
Not the Algorithm, but the Dictionary
A clean distinction is worth making here, because the topic often gets mixed up with the choice of algorithm. Whether Gzip, Brotli or Zstandard produces the smallest file and how content-encoding negotiation works is a separate question - answered in the article on Brotli and Zstandard for text compression. Compression Dictionary Transport does not change the algorithm. It changes the dictionary that is compressed against.
Every compressor replaces recurring byte sequences with back-references to things it has already seen. Brotli ships a built-in default dictionary of common web fragments for this - but it knows neither your framework code nor your class names. A dictionary consisting of your own previous version knows both exactly. The compressor then no longer has to describe the file, only its difference from the known version. That is why the specification speaks of delta compression, and why the jumps are so large: compression does not start from zero, it starts just short of the finish line.
What is new here - and what is not
dcb for Dictionary-Compressed Brotli and dcz for Dictionary-Compressed Zstandard (IETF RFC 9842). Both use the same compression methods as before, just with an external dictionary. A dcb response starts with a 36-byte header containing four magic bytes and the 32-byte SHA-256 hash of the dictionary used (IETF RFC 9842); for dcz it is 40 bytes. This lets the client verify before decompressing whether it really holds the matching dictionary.How a Delta Response Flows
The exchange runs in three steps and needs no client-side JavaScript. First, the server flags a delivered file as a dictionary candidate via the Use-As-Dictionary header. The mandatory match parameter is a URL pattern - regular expressions are explicitly ruled out (IETF RFC 9842) - and describes which future requests may use this file as a dictionary. Optionally, match-dest narrows it to specific fetch destinations, for example scripts only, and id attaches a server-side identifier of up to 1024 characters (IETF RFC 9842), which the client later echoes back via Dictionary-ID.
On the next deploy, the browser requests the new, differently hashed file. If it matches the stored match pattern, it sends - alongside the extended Accept-Encoding - the Available-Dictionary header carrying the SHA-256 hash of exactly one dictionary, the best matching one (IETF RFC 9842). From this the server knows which previous version the client holds, compresses the new file against it and responds with Content-Encoding: dcb. The browser reconstructs the complete file from that; only the delta crossed the wire.
# 1) The server flags the delivered bundle as a dictionary candidate
HTTP/2 200 OK
Content-Type: application/javascript
Content-Encoding: br
Use-As-Dictionary: match="/assets/app.*.js", match-dest=("script")
Cache-Control: public, max-age=31536000, immutable
# 2) Next deploy: the browser announces which dictionary it holds in cache
GET /assets/app.9f2c1d.js HTTP/2
Accept-Encoding: dcb, dcz, br, gzip
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:
# 3) The server replies with the delta against exactly that dictionary
HTTP/2 200 OK
Content-Type: application/javascript
Content-Encoding: dcb
Vary: Accept-Encoding, Available-DictionaryTwo conditions are non-negotiable. The mechanism works only in secure contexts, meaning over HTTPS (IETF RFC 9842). And dictionaries are bound to their origin: the match pattern can only target resources of the same origin, and the cache partitions them. The specification even requires clients to treat dictionaries the same way they treat cookies - including partitioned storage and clearing when cookies are cleared (IETF RFC 9842). A dictionary is therefore not a tracking vector across site boundaries.
Pattern 1: Delta Against the Previous Version
The first and simpler pattern needs no additional file: the dictionary is the old version of the bundle the user has already loaded anyway. It fits hashed build assets - the main JavaScript, the CSS, large vendor chunks. Its charm lies in the fact that the dictionary emerges by itself at deploy time: every shipped version is automatically the dictionary candidate for the next one.
The WICG project's example collection shows how large the difference gets. The React bundle of a news portal shrank across a year of version distance from 344 KB with Brotli to 128 KB as a delta - 63 percent less (WICG compression-dictionary-transport). A header bundle of the same site dropped from 90 KB to 2 KB, a reduction of 98 percent (WICG compression-dictionary-transport). For a large video player, delivery fell from 1.8 MB with Brotli to 384 KB across a quarter - and to 172 KB, meaning 90 percent less, when only a week separated the versions (WICG compression-dictionary-transport). Chrome for Developers runs the same maths on a widely used library: version 1.8.3 compressed against 1.7.9 as a dictionary yields just over 4 KiB instead of 53 KiB with ordinary Brotli - a compression ratio of nearly 98 percent (Chrome for Developers).
The shorter the gap between two versions, the smaller the delta. With this pattern, frequent deployment is not a drawback to be cushioned - it is the precondition under which it works best.
These figures also explain why the pattern harmonises with code splitting: small, stable chunks produce tiny deltas, while a single monolithic bundle gets re-evaluated on every change. How to cut bundles sensibly is covered in the article on lazy loading and code splitting.
Pattern 2: A Static Dictionary for Recurring Fragments
The second pattern targets content that has no previous version but is highly similar: product detail pages, search results, list views. Each of these HTML responses is unique, yet the frame - navigation, footer, markup scaffolding, recurring class names - is nearly identical across thousands of pages. Here you build a dedicated dictionary file from a representative sample of real pages, ship it once and reference it via Link: rel="compression-dictionary" or <link rel="compression-dictionary">. From then on, all matching HTML responses are compressed against that dictionary.
The most prominent real-world case is Google Search itself, which has used a static dictionary built from a sample of real result pages since spring 2025 and keeps it fresh through an automated pipeline that updates multiple times a day. Across all Chrome users, average HTML delivery dropped by 23 percent (Chrome for Developers) compared with standard Brotli; Largest Contentful Paint improved by 1.7 percent overall and by up to 9 percent on high-latency networks (Chrome for Developers). The authors' framing is notable: these gains were achieved on an already hyper-optimized site - less optimized websites would have correspondingly more headroom (Chrome for Developers).
The WICG collection confirms this for shop pages. With a 1 MB dictionary, product pages of a large marketplace fell from 84 KB to 10 KB in transfer size, other examples land between 60 and 90 percent savings; search result pages became 30 to 66 percent smaller (WICG compression-dictionary-transport). For dynamic HTML this is the more interesting lever, because it also applies on the first visit to a subpage - unlike the delta pattern, which only pays from the second deploy onwards. In single-page applications, the measurement side deserves a look too: how vitals can be captured across view transitions at all is shown in the article on soft navigations in SPAs.
| Criterion | Delta against previous version | Static dictionary |
|---|---|---|
| The dictionary is | the old version of the same file | a purpose-built collective file |
| Announced via | Use-As-Dictionary on the asset | Link: rel=compression-dictionary |
| Ideal for | hashed build bundles (JS, CSS) | HTML fragments, product and list pages |
| On the first visit | no gain, plain Brotli response | the dictionary is loaded once in addition |
| Documented savings | 63 to 98 percent (WICG) | 30 to 66 percent on search results (WICG) |
| Maintenance effort | emerges automatically at deploy time | a pipeline must keep the dictionary fresh |
Server Configuration and Integrity Checking
The configuration consists of three building blocks: flagging dictionary candidates correctly, providing delta compression and separating caches cleanly. The first block is pure header work and can be handled in nginx or at the application layer. The second requires the delivering layer to be able to produce dcb or dcz - either precomputed at build time for known version pairs, or in real time at the edge.
# Pattern 1: offer hashed build assets as a dictionary for the next deploy
location ~ ^/assets/app\..+\.js$ {
add_header Use-As-Dictionary 'match="/assets/app.*.js", match-dest=("script")' always;
add_header Vary "Accept-Encoding, Available-Dictionary" always;
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
# Pattern 2: serve a separate, static dictionary for HTML fragments
location = /dictionaries/shell.dat {
add_header Use-As-Dictionary 'match="/product/*", match-dest=("document")' always;
add_header Cache-Control "public, max-age=2592000" always;
}
# Announce it in the document - via Link header or <link rel="compression-dictionary">
# Link: </dictionaries/shell.dat>; rel="compression-dictionary"Integrity checking is not an add-on here but built in: because the SHA-256 hash of the dictionary sits in the header of every dcb and dcz response (IETF RFC 9842), the client detects immediately when response and dictionary do not match - for instance because a cache served the wrong variant. For dcz, the specification additionally requires support for decompression windows of at least 8 MB or 1.25 times the dictionary size, capped at 128 MB (IETF RFC 9842). Anyone planning dictionaries should therefore size them deliberately rather than arbitrarily large.
- Set
Use-As-Dictionaryonly on files that work as dictionaries - versioned bundles, not every small file - Keep
matchpatterns narrow and restrict them withmatch-destto the right fetch destination - Send
Vary: Accept-Encoding, Available-Dictionaryon every cacheable delta response - Weigh dictionary size against benefit - bigger is not automatically better
- For pattern 2, automate dictionary generation so it does not go stale
- After rollout, verify against real responses which
Content-Encodingactually arrives
Cache Pitfalls: Vary and the Variant Explosion
The trickiest part sits not in the browser but in the intermediary layer. A dcb response is usable only by exactly the client that holds exactly that dictionary. If a CDN accidentally serves it to a different user, that user cannot decompress it. This is why the specification mandates a Vary header for cacheable responses, preventing caches from serving dictionary-compressed resources to clients without the matching dictionary (IETF RFC 9842). In practice it reads Vary: Accept-Encoding, Available-Dictionary (MDN Web Docs).
Every dictionary version creates its own cache variant
Vary can lose its hit rate this way. That is why dcb for heavily shared objects is often computed at the edge rather than stored - or you deliberately limit how many previous versions deltas are offered against at all.That makes adoption an architecture decision rather than a header decision. Anyone running a multi-tier cache landscape should clarify beforehand how edge caching in a shop copes with the additional variants and whether the edge can handle compression itself. For upstream caches in your own stack, the same principle applies as with other variant-rich responses - see caching strategies with Varnish and Redis.
The Fallback for Browsers Without Support
Compression Dictionary Transport is a case of progressive enhancement, and that is the reassuring part. A browser without support sends neither Available-Dictionary nor dcb in Accept-Encoding - and thus automatically receives the ordinary Brotli or Gzip response. There is no special path to build and no user who gets excluded. The fallback is the existing compression.
Realistically, reach remains a subset. The mechanism is supported in Chrome and Edge from version 130 and in further Chromium browsers; Firefox and Safari currently provide no support, which results in global coverage of around 69 percent (Can I use). For some users, then, nothing changes - and the rest benefit without anyone carrying a risk. That is a similarly asymmetric benefit-risk ratio to any staged compression chain of Brotli, Zstandard and Gzip.
- Client supports dcb and holds a matching dictionary: delta response, smallest transfer
- Client supports dcb but holds no matching dictionary: ordinary Brotli response
- Client supports Brotli but not dictionaries: ordinary Brotli response
- Client without Brotli or on an unencrypted connection: Gzip response
- Intermediary cache without variant separation: fall back conservatively to the non-dictionary variant
Where the Effort Pays Off - and Where It Does Not
The mechanism is not a standard step for every website, and selling it as one would be dishonest. The gain arises from the interplay of three factors: how large the bundle is, how often you deploy, and how many users come back. If any one of these is small, little of the effect remains. A brochure website with 60 KB of JavaScript and two deploys a year gains nothing measurable - there the levers sit with images, fonts and server response time.
A frequent deploy rhythm
The shorter the gap between two versions, the smaller the delta - at weekly distance the examples document up to 90 percent savings versus Brotli (WICG compression-dictionary-transport). Anyone shipping daily already has the effect built in.
Large bundles with a stable core
Shops and SaaS interfaces carry extensive frameworks and vendor chunks that barely change between releases. Exactly this stable share folds down into the delta, while the changed remainder compresses normally.
Control over headers and edge
What is needed is HTTPS, your own response headers and a cache layer that separates variants cleanly (IETF RFC 9842). Where that control is missing - on rigid third-party hosting, for instance - the mechanism is hard to implement properly.
The other side deserves equally honest billing. With infrequent deploys, small bundles or predominantly new visitors, the benefit is close to zero. Where the cache landscape is complex, variant separation can cost more effort than the bytes bring in. And if a shop primarily suffers from backend time, a smaller bundle helps little - then database, rendering and response time come first, as regularly becomes visible with Shopware performance. One more aspect concerns server load from automated access: dictionaries work for users with a cache, not for the growing number of AI crawlers without cache history.
Placing It in the Compression Stack
Shared dictionaries replace nothing - they add a layer on top. Brotli at maximum level remains the right answer for static assets, zstd or mid-level Brotli for dynamic responses, Gzip the universal fallback. Compression Dictionary Transport adds a fourth, specialised tier to that ladder, one that only engages when client and server share a dictionary. Everything else carries on unchanged.
The sensible order is therefore clear: first the fundamentals - compression active at all, assets pre-compressed, cache headers correct, bundle sensibly split - then this layer. Introducing it too early means optimizing the delta of a bundle that is simply too large. In our projects we therefore first assess bundle structure and deploy rhythm in a technical analysis and decide from there whether dictionary delivery pays off. If the answer is positive, we set it up including the fallback as part of frontend optimization and measure the transfer gain against real responses - embedded in the wider performance services around servers, caching and delivery.
Sources and Studies
Available-Dictionary header and receive the ordinary Brotli or Gzip response.dcb or dcz and cache cleanly per dictionary variant - controlled via Vary: Accept-Encoding, Available-Dictionary (MDN Web Docs). Where that control is missing, on rigid third-party hosting for example, the mechanism stays out of reach. In that case, pre-compressed assets and clean cache headers usually deliver more than trying to retrofit dictionaries.