frontend: content-hash assets, correct cache headers, enable gzip#182
Merged
Conversation
…le gzip - Vite output is now `assets/[hash].(js|ext)` (no source-derived `[name]` prefix). Filenames change only when bytes change, so unchanged chunks and assets keep their URLs across deploys and stay cached. - nginx serves index.html and unhashed root statics (favicon.png, banner.webp) with `Cache-Control: no-cache`, fixing two bugs: stale index.html pinning users to vanished chunk URLs after a deploy (NS_BINDING_ABORTED, "Unable to preload CSS …"), and unhashed images being served `immutable, max-age=31536000` so updates never reach cached clients. - `/assets/` location switched to `^~` so the prefix wins over regex location matching; the catch-all extension regex (which was masking the prefix and applying immutable to unhashed files) is removed. - nginx gzip on for js/css/json/svg/wasm/fonts; `gzip_static on` so any future precompressed artefacts are served automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three related fixes that together stop the broken-deploy symptoms reported on
v2.esa-blueshell.nl(NS_BINDING_ABORTED, Unable to preload CSS for /assets/VCard-…css, dynamic-import failures) and improve cache hit rate / serving cost.1. Pure content-hash asset filenames (
vite.config.mjs)Vite/Rollup default to
assets/[name]-[hash].js— the[name]is the source module's filename, so a rename likeEvents.vue → EventsPage.vuechanges the URL even though the bytes are identical. Switched toassets/[hash].jsandassets/[hash][extname]. Filenames now change iff content (or transitively imported content) changes, so unchanged chunks and assets keep their URLs across deploys and stay cached in browsers and at the edge.2. Correct
Cache-Controlforindex.htmland unhashed root statics (nginx.conf)The previous config had two latent bugs:
index.htmlwas served with noCache-Controlheader (verifiedcf-cache-status: DYNAMIC, no header). Browsers fall back to heuristic caching fromLast-Modified. After a deploy, users hold a staleindex.htmlreferencing chunk hashes the new build no longer ships →NS_BINDING_ABORTEDcascade and Unable to preload CSS …. This is the root cause of the reported symptoms.~* \.(js|css|png|jpg|...|webp|...)$matched both hashed/assets/*files and unhashed/banner.webp,/favicon.pngfrompublic/, applyingCache-Control: public, max-age=31536000, immutableto all of them. Unhashed files with stable URLs cannot beimmutable— once cached, clients never see updates for a year. Confirmed in production:Fix:
/assets/location is now^~so the prefix wins over any regex (in nginx, regex~*beats unmodified prefix). Long-immutable cache stays on this prefix only.location /(SPA fallback + unhashed root statics) sendsCache-Control: no-cache. ETag/Last-Modifiedmake 304 revalidations cheap.3. Origin gzip (
nginx.conf)nginx had no
gzipconfig, so cache misses at Cloudflare and direct origin hits were uncompressed. Enabledgzip onfor js/css/json/svg/wasm/fonts andgzip_static onso any future precompressed.gzartefacts are picked up automatically. Cloudflare still does brotli at the edge for cache hits.Why these are bundled
All three changes share the same domain (frontend caching/serving) and the same review surface (
vite.config.mjs+nginx.conf). Splitting them would just create churn — and in particular, removing[name]from filenames without also fixing the stale-index.htmlproblem would still leave deploys broken for users with a cached HTML.Out of scope (possible follow-ups)
build.rollupOptions.output.manualChunksto split outvue,vuetify, etc. into vendor chunks that change only when those deps change. Real win for repeat visitors, but bundle-size analysis should drive it. Happy to do as a follow-up.build.sourcemap: 'hidden'so anonymous chunk filenames are still debuggable from devtools / error trackers.vite-plugin-compressionto ship precompressed.gz/.brartefacts;gzip_static onis already in place to consume them.Test plan
yarn buildlocally — verifydist/assets/contains.js/.css/asset files named purely as<hash>.<ext>with no source-derived prefix.curl -sI http://localhost:3000/returnscache-control: no-cache;curl -sI http://localhost:3000/assets/<hash>.jsreturnsimmutable, max-age=31536000;curl -sI http://localhost:3000/banner.webpreturnsno-cache.curl -H 'Accept-Encoding: gzip' -sI http://localhost:3000/assets/<hash>.jsreturnscontent-encoding: gzip.v2.esa-blueshell.nl, confirm noNS_BINDING_ABORTED/ preload errors. Force-refresh and verify hashed assets return 200 from cache,index.htmlshows a 304 revalidation on subsequent loads./assets/<hash>.*URLs are reused (304 / disk cache) and only changed chunks get new filenames.🤖 Generated with Claude Code