performancegzipbrotlicompressionhttp

Gzip vs. Brotli for JSON APIs — The Algorithm Differences That Actually Matter

·8 min read·Performance

"Just enable gzip or brotli" undersells the decision

Most performance guides land on "minify your JSON and enable gzip or Brotli" and stop there, as if the two were interchangeable checkboxes. They compress similarly well on JSON in the common case, but they get there differently, and that difference matters most exactly where a lot of real APIs live: compressing a unique, dynamically generated response on every single request, not a static file served from cache.

How each algorithm actually works

Gzip is DEFLATE — LZ77 (finds and back-references repeated byte sequences within a sliding window) followed by Huffman coding (shorter codes for more frequent bytes). It's been the web standard since the 1990s, implemented everywhere, and its behavior is predictable: fast, moderate compression ratio, low CPU cost even at higher levels.

Brotli does the same LZ77 + Huffman combination, but adds two things gzip doesn't have: a much larger sliding window (up to 16MB vs. gzip's 32KB, letting it find repeated patterns much further apart in a large payload) and a built-in static dictionary of roughly 13,000 common strings and phrases pulled from real web content — HTML tags, common JavaScript/CSS tokens, and common words. That dictionary is why Brotli tends to win specifically on JSON API responses: repeated key names, common English words in string values, and JSON's own punctuation patterns overlap heavily with what the dictionary was built from, giving Brotli a head start gzip doesn't get.

The tradeoff nobody puts in the checklist

Brotli's compression ratio advantage is real, but it comes at a quality-level cost curve that's much steeper than gzip's. At maximum quality (11), Brotli produces noticeably smaller output than gzip — but takes meaningfully longer to compress, which is fine for a static asset compressed once at build time and served from cache thousands of times, and a real problem for a JSON API response generated fresh on every request, where that compression time is added to every single response's latency.

text
Static asset (compressed once, served many times):
  → Brotli quality 11 — maximize ratio, compression time doesn't matter

Dynamic API response (compressed fresh, every request):
  → Gzip level 6, or Brotli quality 4-5 — balance ratio against added latency

Most compression middleware defaults land here reasonably: Node's compression package uses gzip level 6 by default; nginx's brotli module defaults to a low-to-mid quality level specifically because it's usually applied to dynamic content. The mistake is copying a "brotli quality 11" config example meant for static assets onto a dynamic JSON API and being surprised by added response latency.

Content negotiation: the client decides, not the server

A server doesn't unilaterally pick one — the client advertises what it supports via the Accept-Encoding header, and the server picks the best one it also supports:

bash
curl -H "Accept-Encoding: br, gzip" -I https://api.example.com/users
# Content-Encoding: br     → server chose Brotli
# Content-Encoding: gzip   → server chose gzip (older client, or br unsupported)
javascript
// Express — both available, negotiated automatically per-request
import compression from "compression";
import shrinkRay from "shrink-ray-current"; // adds brotli support on top of gzip-only "compression"

app.use(shrinkRay()); // picks brotli when the client supports it, gzip otherwise

Brotli support is now essentially universal in modern browsers and is supported by every major CDN (Cloudflare, Fastly, CloudFront), but always keep gzip as the fallback — some non-browser HTTP clients, older proxies, and internal service-to-service callers still only send Accept-Encoding: gzip.

Frequently asked questions

Yes, though the marginal benefit shrinks — compression already eliminates most of the redundancy that minification targets (repeated whitespace compresses extremely well on its own), but minifying first still reduces the amount of data the compressor has to process, which matters slightly for CPU cost on very large payloads. See JSON Minify vs. Format for concrete before/after numbers on that layer specifically.

In the vast majority of real-world cases, yes, mainly due to the built-in dictionary favoring web-like text — but the gap narrows or can reverse on data that doesn't resemble typical web content (e.g., largely random strings, UUIDs, base64-encoded binary blobs), where neither algorithm's dictionary or pattern-matching has much to work with.

It works over both — Brotli is purely a Content-Encoding, unrelated to TLS. In practice almost all Brotli deployments are also HTTPS, but that's a separate, unrelated best practice, not a requirement of the compression itself.

It varies by payload size and CPU, but it's exactly why quality 11 (Brotli's max) is discouraged for anything compressed per-request rather than once and cached — benchmark your own real payload rather than trusting a rule of thumb, since the right quality level depends on your response sizes and request volume.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.