Skip to content

CONCEPT Cited by 1 source

Bundler chunk invalidation

Bundler chunk invalidation is the phenomenon where a small source-code change causes a JavaScript bundler (webpack, Vite, Rollup, esbuild, Turbopack, …) to re-chunk the application such that asset filenames change even when most of the content is unchanged. Every client's browser sees a new URL for the bundle, cache keys miss, and the full bundle is re-downloaded — even though 99 %+ of the bytes are identical to what the client already has cached.

Canonical framing: this is the root cause for why caching has "effectively opted out" in high-deploy-frequency production environments, and the motivating problem for shared-dictionary compression.

How the mechanism works

Modern JS bundlers emit assets with content-addressed filenames:

app.a7b3c9f.js
vendor.8e1d7a2.js

The hash suffix is computed from the chunk's contents (or, in some configurations, from the content + module graph). This is a deliberate cache-busting strategy: when the content changes, the filename changes, so the browser can't accidentally serve stale content from cache.

The problem: the hash changes for the whole chunk when any byte in the chunk changes. A one-line fix in a single module that happens to sit in the app.js chunk produces a new app.a7b3c9f.jsapp.e4f2d81.js filename, even though 99.9 % of the bytes are identical.

More aggressively: many bundlers re-chunk when the module graph shifts, not just when contents change. Adding or removing an import can redistribute modules across chunks, which rewrites every chunk's content, which rewrites every chunk's hash, which rewrites every filename.

Why this breaks caching

Standard HTTP caching is URL-keyed. A cached app.a7b3c9f.js is dead the moment the deploy emits app.e4f2d81.js — the client has no way to know the new URL is almost the same as the old one. It fetches from scratch.

  • High-deploy-frequency amplification: a team shipping ten deploys a day invalidates every client's bundle cache ten times a day. "Ship ten small changes a day, and you've effectively opted out of caching." (Source: this article)
  • AI-assisted-development amplification: AI coding tools compress the interval between commits; CI/CD pipelines ship more frequently; effect compounds.
  • Mandatory-upgrade amplification: security patches, dependency bumps, feature flags flipping all trigger re-chunks, not just intentional product changes.

Why traditional compression can't help

Per-response compression (gzip / Brotli / Zstandard) looks at the bytes of the response in isolation. It has no memory of what the client has cached. It compresses app.e4f2d81.js from scratch as if the client had never seen anything before — producing the same ~92 KB gzip output it did for app.a7b3c9f.js, even though the actual new information is a few KB of diff. "Traditional compression helps with the size of each download, but it can't help with the redundancy. It doesn't know the client already has 95 % of the file cached."

Why shared-dictionary compression fixes it

Shared-dictionary compression + [[systems/rfc-9842-compression-dictionary- transport|RFC 9842]] let the server compress the new version against the previous cached version using the previous response as the dictionary. Even though the URL has changed, the browser still has the previous response cached under a different URL; via Use-As-Dictionary: match=, the dictionary applies to the new URL. Only the diff goes on the wire.

Result per Cloudflare's 2026-04 lab: - 272 KB bundle with a few localised changes → 2.6 KB DCZ (97 % over gzip). - 94 KB bundle with only a config block changed → ~159 bytes (99.5 % over gzip) — canicompress.com.

Seen in

  • sources/2026-04-17-cloudflare-shared-dictionaries-compression-that-keeps-up-with-the-agent — bundler chunk invalidation is named as the canonical root cause shared-dictionary compression is designed to fix: "the bundler re-chunks, filenames change, and every user on earth could re-download the entire application. Not because the code is meaningfully any different, but because the browser/client has no way to know specifically what changed. It sees a new URL and starts from zero."
Last updated · 200 distilled / 1,178 read