Cache busting changes a JavaScript file's URL whenever its content changes, so browsers and CDN edges fetch the new release instead of serving a stale copy. As of 2026, the standard pattern pairs content-hashed filenames cached for one year (Cache-Control max-age of 31,536,000 seconds plus the immutable directive from RFC 8246) with an HTML entry document that is revalidated on every request. Most stale-bundle incidents come from getting the second half wrong.
This guide covers the full set of CDN JavaScript best practices. It compares hashed filenames, query strings and versioned paths in a decision matrix and gives cache rules for single-page apps (SPAs). It also includes an asset-retention calculation, a deploy-order invalidation checklist, and a diagnostics and rollback procedure you can apply to any bundler or framework.
A CDN edge and a browser both key their caches on the URL, with the query string handled differently depending on configuration. If the URL stays the same while the bytes change, every cache layer keeps serving the old bytes until its TTL expires or someone purges it. Cache busting removes that dependency. A new release gets a new URL, and the old URL can stay cached forever because nothing will ever request it for new code.
This splits your files into two classes with opposite cache policies. Fingerprinted assets (bundles, chunks, CSS, fonts with hashes in their names) are immutable: cache them for a year at every layer and never purge them. Entry points (index.html, the service worker file, any unhashed manifest or loader) are mutable. Keep them short-lived or revalidated, and purge them on every deploy. The HTML is the pointer, and the hashed files are the content it points to.
Under this model, a release becomes visible as soon as the pointer is fresh. Freshness is bounded by the entry document's TTL, not by the asset TTL. That is why a one-year max-age on a bundle is safe.
There are three common ways to change a JavaScript URL per release. They differ in cache-key safety, how well old and new versions coexist, and how much CDN configuration they depend on.
| Method | Example | Busts only on real change | Old and new coexist at origin | Depends on CDN cache-key config | Best fit |
|---|---|---|---|---|---|
| Content-hashed filename | main.a1b2c3.js | Yes, per file | Yes | No | Default for any built app |
| Versioned path | /v2.3.0/app.js | No, every file moves per release | Yes | No | Public SDKs and embeddable scripts with semantic versioning |
| Query string | app.js?v=2.3.1 | Only if the version is a content hash | No, one file is overwritten | Yes, the query must be in the key | Legacy sites without a build step |
Content-hashed filenames are the only method that is safe without CDN-specific configuration and keeps old and new releases side by side during a rollout.
Versioned paths are a legitimate choice for third-party embeds. Customers can pin to /v2/ or /v2.3.0/, and you publish the minor-version alias with a short TTL. Use them for the public contract and hashed filenames inside the bundle.
A cache busting query string works only when the CDN includes the query string in its cache key. Many CDN zones ignore or strip query strings by default to raise hit ratios. It also overwrites a single file at origin, so during deploy an old HTML page can fetch new code, or a new key can cache old bytes.
That race condition is the deeper problem. If the new HTML reaches users before the new app.js lands on every origin node, the edge caches the old file under the new query key for its full TTL. Hashed filenames make this impossible because the new URL does not exist until the file does.
For SPA CDN caching, give hashed assets a one-year immutable lifetime and give index.html a policy that forces revalidation. Then purge index.html at the edge on each deploy. The entry document decides which release a user runs, so its TTL is your effective release propagation time.
The last rule prevents the worst SPA failure. A request for a deleted chunk falls through to the HTML fallback, returns 200 with text/html, and the edge caches that HTML under the chunk URL. Users then see a JavaScript syntax error ("Unexpected token") instead of a clean 404 that your loader could retry.
Lazy-loaded chunks break when a user's open tab references a chunk that you deleted after a newer deploy. To avoid this, retain old assets for at least the longest realistic session plus the entry-document TTL. With 10 deploys a week and tabs that stay open for up to 7 days, keep the last 10 releases at minimum. Our recommendation is 20 for margin.
The storage cost is negligible. At 3 MB of hashed assets per release, 20 releases total 60 MB, and only changed chunks get new hashes, so the real figure is usually lower. Deleting old assets on every deploy saves almost nothing and guarantees chunk-load errors for users who started their session before the deploy.
Cache busting decides which bytes users get. The build pipeline decides how many bytes they download. Automate all of the following in CI so filenames, hashes and source maps come from the same build:
Subresource Integrity (SRI) puts a sha384 hash of the file in the integrity attribute of the script element. The browser refuses to execute bytes that do not match. Cross-origin scripts also need the crossorigin attribute set to anonymous, and the CDN must return a matching Access-Control-Allow-Origin header.
SRI pairs naturally with hashed filenames, since both assume one URL maps to one set of bytes forever. The hash is computed over the decoded body, so Brotli or gzip at the edge does not break it. Any edge feature that rewrites JavaScript content, such as automatic minification or script injection, will break it. Turn those off for SRI-protected paths.
Deploy in dependency order: content first, pointer last. Purge only the pointer. This sequence makes a release atomic from the user's point of view without a global cache flush.
Steps 3 and 5 are where the CDN API matters. BlazingCDN includes purge and cache warm-up through its REST API on every plan, along with origin shield and request coalescing that absorb the burst of misses when a new release goes live. Its CDN features for cache rules, purge and warm-up page lists what is included per zone.
When users report stale code, check response headers before you touch the cache. Fetch the entry document and one hashed bundle from your HTTP client of choice in each region you serve. Record Cache-Control, Age, ETag, Content-Type and the CDN's cache-status header.
Rollback under this model needs no asset changes. Republish the previous index.html (its hashed assets are still within retention), purge the entry files again, and verify. If a bad service worker shipped, publish a corrected worker that calls skipWaiting and clears its caches, because purging the CDN does not evict a worker already installed in the browser.
Track three numbers before and after the change: edge cache hit ratio on the assets path (hashed files should sit close to fully cached), median and p95 time to fresh release (deploy timestamp to the first real-user beacon carrying the new build ID), and the chunk-load error rate per deploy. Lighthouse and WebPageTest give lab LCP and transfer sizes. Real-user monitoring is the only reliable source for Interaction to Next Paint (INP), which replaced First Input Delay as a Core Web Vital in March 2024. Time to Interactive was removed from Lighthouse scoring in 2023, so compare against LCP and INP rather than older TTI baselines.
Test on throttled mobile profiles and in your slowest region. Cache policy failures show up first where round trips are longest.
Cache busting means giving a JavaScript file a new URL whenever its content changes, so browsers and CDN edges download the new version instead of reusing a cached one. The usual method embeds a content hash in the filename, which lets the file be cached for a full year while the HTML that references it stays fresh.
Use a hashed filename whenever you have a build step. A cache busting query string depends on the CDN including queries in its cache key, and it overwrites one file at origin, which creates race conditions during deploys. Query strings remain acceptable for legacy pages without a bundler, provided the value is a content hash.
Cache hashed JavaScript bundles for one year with Cache-Control public, max-age 31536000 and the immutable directive. Because the filename changes whenever the content changes, a long lifetime cannot serve stale code. Never purge these files on deploy, since purging forces needless origin fetches and lowers hit ratio right when a release drives traffic.
Serve the single-page app's index.html with no-cache for browsers, so every load revalidates with ETag, and a short edge TTL of 30 to 300 seconds. Purge it on every deploy. Its freshness controls how quickly users pick up a new release, so a long TTL on index.html delays every deployment.
No. Subresource Integrity hashes the decoded response body, so Brotli, gzip or zstd compression at the CDN edge does not affect validation. Features that rewrite JavaScript bytes, such as automatic edge minification or script injection, will cause integrity failures and blocked scripts. Disable those features on paths served with integrity attributes.
Pull headers for index.html, your service worker and three hashed chunks from two regions, and write down Cache-Control, Age, ETag and Content-Type for each. Then deploy a trivial change and time how long until real-user beacons report the new build ID. If that number exceeds your index.html TTL, or any asset URL returns text/html, you have found the fix to make first. Next, check your asset retention against the longest session in your analytics.
If you want to run the same deploy-order and purge workflow on another delivery layer, the BlazingCDN Anycast CDN for static assets page describes pull caching with API purge, warm-up and custom cache rules set up with an engineer.