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.
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.
- Use content-hashed filenames. Like
app.a8f9b2.js, where the hash is derived from the file’s contents. Change the content and the name changes; leave it unchanged and the name stays. That lets you cache every asset forever withCache-Control: public, max-age=31536000, immutable— theimmutabledirective tells the browser “don’t even revalidate this,” cutting needless conditional requests. - The one file you must NOT cache long: the HTML/entrypoint. Make the HTML that references the hashed assets (or the entrypoint that references the manifest) short-lived or
no-cache. The logic is simple: the HTML must always be fresh so it points to the NEW hashes; the assets already carry the right version via the hash. Cache the HTML long and the whole chain locks up. - CI/CD: upload first, then go live. Build hashed output with the Vite manifest. The order is critical: upload the assets to the CDN/bucket BEFORE flipping the app live. Otherwise the new HTML points to a hash that isn’t there yet and you get 404s. In a DeployerPHP flow, wire this as an “upload before switching the symlink” step.
- 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. If you don’t delete the previous release’s assets, these in-flight users don’t break. DeployerPHP’s
keep_releasesalready gives you exactly this.
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.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.