Shared Brotli Dictionaries: Advanced Payload Optimization Strategies
Shared Brotli Dictionaries offer a way to shrink payloads beyond standard compression. Learn to implement dictionary-based compression for faster web performance.
We spent three weeks trying to shave 150ms off our Largest Contentful Paint (LCP) for a dashboard application. We’d already exhausted standard Gzip and Brotli compression, and our tree-shaking was as aggressive as it could get without breaking the build. That’s when we started experimenting with Shared Brotli Dictionaries.
Standard compression algorithms like Brotli work by looking for patterns within a single file. But when you’re shipping a massive React bundle or a complex JSON schema, the browser starts from scratch every time. Shared Brotli Dictionaries change the game by providing a pre-shared "context" to the decompression algorithm, allowing it to recognize common patterns across multiple requests.
Understanding Shared Brotli Dictionaries
When you use Content-Encoding: br without a dictionary, the compressor has to store its own window of previously seen data. It's efficient, but limited by the file size itself. Shared Brotli Dictionaries allow the server and client to agree on a "base" vocabulary before the request even happens.
If your application has a large, static library—like a massive D3.js implementation or a shared design system—you can package that common code into a dictionary. Subsequent payloads that reference this dictionary effectively "know" the common syntax, leading to compression ratios that are often 20-30% better than standard Brotli.
The Implementation Path
We didn't get it right on the first try. We initially tried to use a generic dictionary generated from our entire codebase, but the overhead of downloading the dictionary itself negated the payload savings. We learned that the key to effective Shared Brotli Dictionaries is granularity.
Here’s the basic flow of the Compression Dictionaries API:
Flow diagram: Client requests dictionary → Server serves dictionary with header; Server serves dictionary with header → Browser caches dictionary; Browser caches dictionary → Client requests asset with 'Available-Dictionary' header; Client requests asset with 'Available-Dictionary' header → Server compresses asset using dictionary; Server compresses asset using dictionary → Client decompresses asset using cached dictionary
To implement this, you need to set the Use-As-Dictionary response header. This tells the browser: "Store this blob, it's useful for future requests."
- Generate the Dictionary: You'll need a tool like
brotli(the CLI) to train your dictionary on a representative sample of your application's traffic. - Serve the Dictionary: Use a long cache-control header. You want this to stick around.
- Request with Header: When the browser sends a request, it includes the
Available-Dictionaryheader, which contains the ID of the dictionary it has in storage. - Server-Side Compression: Your origin server (or edge worker) checks the header, loads the dictionary into the Brotli encoder, and streams the compressed response.
Trade-offs and Lessons Learned
Our first attempt at Payload Optimization backfired because we didn't account for the "dictionary miss" scenario. If a user clears their cache or is on a first visit, they have to download the dictionary plus the asset, which actually makes the initial load slower.
We found that for most users, the savings on the second or third navigation were significant—roughly 1.8x smaller bundles—but the first-load penalty was about 40ms. We mitigated this by only injecting the dictionary-aware headers for return visitors or by preloading the dictionary during idle time.
If you're already familiar with Compression Streams API: Optimizing JavaScript Performance and Payloads, you'll find the logic for handling streams quite similar. The primary difference is that the Compression Dictionaries API handles the context-sharing at the browser/server layer, whereas the Streams API is often used for manual chunk processing.
Comparison: Compression Techniques
| Technique | Payload Reduction | Implementation Effort | Complexity |
|---|---|---|---|
| Gzip | 60-70% | Low | Minimal |
| Standard Brotli | 75-80% | Low | Minimal |
| Shared Brotli | 85-95% | High | High |
Performance and Web Performance Considerations
Implementing Web Performance improvements at this level requires rigor. You aren't just changing a config; you're changing the state machine of how assets are delivered.
If you are currently working on INP Optimization: Architecting Browser-Level Task Slicing, keep in mind that dictionary-based decompression can be slightly more CPU-intensive for the browser. While the network savings are massive, the main-thread cost of decompressing a dictionary-optimized payload is non-zero. Always profile your LCP and TBT (Total Blocking Time) after deploying these changes.
We also looked at WordPress REST API Performance: Brotli Compression for Headless SaaS to see if we could apply dictionary compression to our API responses. It turns out that for highly repetitive JSON structures, the gains are even more dramatic than for JavaScript files. If your API returns consistent shapes, this is a massive win for mobile users on high-latency networks.
Final Thoughts
The Compression Dictionaries API is still evolving, and support is primarily limited to modern Chromium-based browsers. If you're building a dashboard or an enterprise SaaS where you control the user's browser environment, it’s a no-brainer. For a public-facing e-commerce site? You’ll need a robust fallback strategy.
I’m still not convinced that this is the right move for every project. The complexity of managing dictionary versions and cache invalidation adds a significant maintenance burden. We ended up only applying it to our two largest core JavaScript bundles, leaving the rest of the site on standard Brotli. It was a pragmatic compromise that gave us the performance boost we needed without turning our infrastructure into a science experiment.