JavaScript and CSS cache busting: filenames, versions and safe releases
Cache busting gives changed JavaScript or CSS a new URL, commonly by putting a content hash in the filename. Serve those versioned files with a suitable long cache lifetime, and let the HTML discover new versions through a shorter lifetime or revalidation.
In this guide
- Separate the HTML document from versioned assets
- Use caching directives that match that contract
- A query version can work if the cache key includes it
- Diagnose the layer that still serves old bytes
- Keep old pages working during deployment and rollback
- Measure reuse after correctness is established
- Common questions
- Sources & definitions
When a release looks correct in a private window but old in a returning visitor’s browser, compare the asset URLs before changing cache settings everywhere. A new deployment can replace the server’s file while a browser still has a fresh cached response for the old URL.
A reliable release makes the relationship between HTML and its assets explicit. The example below keeps two versions available so that a page opened before deployment can still load its matching files.
Separate the HTML document from versioned assets
Think of HTML as a small manifest pointing to a particular release. Release A references app.4c21.js and site.19ab.css. Release B changes the application and references app.9d80.js; if the stylesheet bytes did not change, it can keep site.19ab.css. The hashes here are illustrative names, not computed values for real files.
This arrangement lets unchanged assets be reused while changed assets acquire new cache identities. It also explains why caching the HTML for a year can delay discovery of the release: a visitor who keeps receiving the old document keeps requesting the old asset URLs.
| Resource | Release A | Release B | Expected treatment |
|---|---|---|---|
| HTML | Points to app.4c21.js | Points to app.9d80.js | Revalidate or use a deliberately short lifetime |
| Application JavaScript | app.4c21.js | app.9d80.js | New URL when bytes change |
| Unchanged CSS | site.19ab.css | site.19ab.css | Reuse the same content URL |
| Older assets | Available | Still available for older pages | Retain through the required compatibility window |
Use caching directives that match that contract
For a public, content-versioned file that will never change at that URL, a long max-age with immutable can be appropriate. For HTML, no-cache permits storage but requires validation before reuse. It is different from no-store, which asks caches not to store the response. Choose the policy for the resource rather than copying one header to the entire site.
The following headers illustrate a strategy, not a universal configuration. Confirm what your hosting platform actually sends and how its edge cache is configured. A response header in a local file is not evidence that the public browser and CDN received the same policy.
Versioned public asset: /assets/app.9d80.js
Cache-Control: public, max-age=31536000, immutable
HTML document: /index.html
Cache-Control: no-cache
The URL /assets/app.9d80.js must continue to serve the same bytes.A query version can work if the cache key includes it
A URL such as /app.js?v=release-b can provide a different cache identity from /app.js?v=release-a. Check whether every relevant cache includes that query parameter in its cache key. A CDN rule that ignores query strings can make both URLs resolve to the same cached object, defeating the intended versioning.
Avoid a random value on every page load. If every visitor requests a unique asset URL, useful cache reuse disappears. A stable content hash or release version changes only when the resource changes. URL fragments such as /app.js#release-b are not sent in the HTTP request and are unsuitable for this purpose.
Diagnose the layer that still serves old bytes
Compare the HTML response, the referenced asset URL and the asset bytes for the affected visitor. If the document references the old hash, investigate HTML freshness. If it references the new hash but receives old content, investigate deployment, cache keys and origin responses. If network responses are correct but behavior stays old, inspect whether an already running page or a service worker controls the experience.
A CDN purge does not remove a fresh response already stored inside every visitor’s browser. Conversely, a browser hard reload is only a local diagnostic and does not fix a release for everyone. Write down which cache was bypassed during each check so that a successful test is reproducible.
- Record the final HTML URL and the script or stylesheet URL it references.
- Check status, Content-Type, Cache-Control, ETag and any available cache-status headers.
- Confirm the requested version returns its intended bytes rather than an HTML fallback page.
- Check service-worker cache rules separately if the application installs one.
- Test both a clean visit and a page left open across the release.
Keep old pages working during deployment and rollback
Upload new assets before publishing HTML that references them, or use a platform deployment that switches the complete release together. Keep the previous assets long enough for older HTML and open tabs to finish using them. An older page may load an additional module hours after its initial visit.
For example, publish B with both A and B assets present, verify B, and retain the ability to point HTML back to A. Deleting app.4c21.js immediately can break a visitor who still has A’s document. Reusing app.4c21.js for new bytes also breaks the content-addressed contract and can make rollback unpredictable.
Measure reuse after correctness is established
Run a small repeat-visit check: the first visit downloads the assets, a second visit can reuse eligible resources, and a new release downloads only the changed versions. Inspect actual network transfers, including revalidation requests, rather than treating every cache response as identical.
The cache-savings calculator can estimate origin request reduction from your own hit-rate assumption. The transfer calculator can estimate bytes for a stated request count and response size. Neither replaces measuring the deployed response headers and the real hit rate; use them to compare an explicit plan after the release behaves correctly.
Common questions
Should I disable all caching to fix stale CSS?
Usually the first step is to identify the stale document or asset and establish stable versioning. Disabling reuse everywhere can add transfer and latency while leaving the release process unclear.
Will changing an ETag immediately replace a fresh cached file?
A browser can reuse a response while it is fresh without asking the server to validate it. A new content URL avoids depending on that future validation step for a changed asset.
Can I delete old hashed files after a deployment?
Only after the compatibility period you need for cached HTML, open pages and rollback. Keep the older files while any supported document can still reference them.
Sources & definitions
Method checked: