Skip to content
Muhammet Şafak
tr
Asked by: Aslı Answered:

How do I set up CDN asset versioning: hashing and a cache strategy on deploy?


Question

When I push new code live with DeployerPHP, the UI breaks because of stale CSS/JS in the user's browser. Some CDNs ignore a query string like `mix.js?id=123` and keep serving the old version. For a permanent fix I want to hash the asset names (`app.a8f9b2.js`) and ship them with `Cache-Control: max-age=31536000` to the CDN. How do I design the build and invalidate automation in CI/CD?

Answer

Short answer: query-string cache busting is unreliable; the durable fix is content-hashed filenames (app.a8f9b2.js) — the name changes only when the content changes, and the cache strategy handles the rest.

Short answer

The root of your problem: with app.js?id=123 the filename is always the same. Some CDNs strip or ignore the query string in the cache key, so even when id changes they keep serving the OLD cached file. You need to change the cache key — the name, not the query. I covered the API side of the same “who refreshes the cache” question in the edge caching and invalidation answer.

Why

  1. A query string is not a cache-key guarantee. Behaviour varies by CDN; some ignore it entirely. The name, on the other hand, is part of the cache key at every layer.

  2. A content hash is a natural version number. The name changes only when the content changes — and when it doesn’t, there’s no needless re-download either.

  3. Deploy time is a race. If the new HTML points at a hash that hasn’t been uploaded yet, the user gets a 404; hashing alone won’t save you if the order is wrong.

What to do

  1. Use content-hashed filenames. Like app.a8f9b2.js, where the hash is derived from the file’s contents. That lets you cache every asset with Cache-Control: public, max-age=31536000, immutable — the immutable directive tells the browser “don’t even revalidate this,” cutting needless conditional requests.

  2. Keep the HTML/entrypoint short-lived. Serve the HTML that references the hashed assets with no-cache or a short TTL: it must always be fresh so it points to the new hashes. Cache the HTML long and the whole chain locks up.

  3. In CI/CD, upload first, then go live. Build hashed output with the Vite manifest and upload the assets to the CDN/bucket before flipping the app live. In a DeployerPHP flow, wire this as an “upload before switching the symlink” step.

  4. Keep the previous release’s assets around for a while. At deploy time, a user with the page open is still running the old HTML and requesting the old hashes. DeployerPHP’s keep_releases protects those in-flight users.

Bottom line: I’d set up the trio of content-hashed immutable-cached assets + short-lived HTML + upload-before-cutover. With true content hashing you practically never need to purge assets — since the name changes, the old file just sits there untouched and nobody requests it. The only thing you ever invalidate is the HTML, and that’s already short-cached. Drop query-string busting entirely.

Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind