Keep the week-long immutable cache to Vite's hashed /assets/ tree - #388
Conversation
This comment has been minimized.
This comment has been minimized.
spa.conf gave every static file `expires 7d` with `public, immutable`, and Cloudflare honours that for the whole window. It is the right policy for Vite's output, whose names carry a content hash, and the wrong one for every file whose name survives a deploy: the dashboard's theme-boot.js and everything the data explorer ships, which has no build step. The 2026-09-15 deploy of the explorer's theme switch showed the failure shape: index.html is never cached, so the edge served the new markup with the 5 September app.css and app.js under it, and the switch rendered as three unstyled slivers until the two URLs were purged by hand. Nothing in the pipeline could see it; every probe of that vhost still read a healthy 200. Immutable now applies only under /assets/, the one tree where the name changes with the content. Everything else static is `no-cache`: with Origin Cache Control on, Cloudflare caches and revalidates on every request, and nginx answers a conditional request with 304, so the cost of an unchanged file is a round trip rather than a re-download, and a changed one shows through at once. The data explorer's vendored libraries lose the long cache too, since their names carry no version either. Three assertions come with it. A rendered-config test pins that immutable is confined to /assets/ and that the catch-all follows it (nginx takes the first regex location that matches, so a catch-all placed first would swallow /assets/ too). The staging smoke probes the live headers on dash's theme-boot.js, the explorer's app.css and one hashed map asset, each with a never-seen query string so the request reaches nginx rather than a copy the edge stored under the old headers; run against staging before this change it fails on theme-boot.js, which is the point. The rendering fixture moves to tests/nginx_helpers.py so both nginx test files share it. The edge does not forget on its own: a copy it already holds under the old headers stays until it expires or is purged, so the first deploy of this is followed by a purge of the affected URLs. ClickUp 123zgec2zqr. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
b6012d8 to
aebbb35
Compare
|
Claude finished @Babissimo's task in 2m 39s —— View job Review: Keep the week-long immutable cache to Vite's hashed
|
#388 switched every static file outside /assets/ to `no-cache`, on the reading that Cloudflare would cache and revalidate at the edge while the browser revalidated with a 304. Half of that is what happens. The edge does revalidate, but the zone's Browser Cache TTL is four hours, and with Origin Cache Control on Cloudflare stamps that on any cacheable response whose own max-age is lower: staging answered `cache-control: max-age=14400` for theme-boot.js and app.css the moment #388 deployed. A browser would sit on a stale copy for four hours after a deploy, and the two smoke probes that assert `no-cache` failed, which skipped the production deploy. `no-store, no-cache` is the form this zone passes through untouched; index.html has been served that way throughout and arrives intact as DYNAMIC. So the unhashed files take the same policy as the page that loads them. The price is that a browser re-fetches them on every visit rather than revalidating, about 450 KB for the data explorer including its vendored libraries, on a low-traffic public page. The smoke probes and the rendered-config test now assert `no-store`. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Why
deploy/nginx/snippets/spa.confgives every static fileexpires 7dwithCache-Control: public, immutable, and Cloudflare honours that for the whole window. That is the right policy for Vite's output, whose names carry a content hash, and the wrong one for every file whose name survives a deploy: the dashboard'stheme-boot.js(fromdashboard/public/) and everything the data explorer ships, which has no build step.Today's deploy of #383 showed the failure shape.
index.htmlis never cached, sodata.retina.fmserved the new markup with the 5 Septemberapp.cssandapp.jsunder it (cf-cache-status: HIT, age ~20 h, expiring 21 September), and the theme switch rendered as three unstyled slivers. The container held the new files and a cache-busted fetch returned them; the edge did not. Nothing in the pipeline could see it, since every probe of that vhost still answered 200. The two URLs were purged by hand through the Cloudflare API, which is why it renders now.What
spa.conf: immutable only under/assets/, the one tree where the name changes with the content. Every other static file isCache-Control: no-cache. With Origin Cache Control on (the default for this plan tier) Cloudflare caches and revalidates on every request, and nginx answers a conditional request with 304, so an unchanged file costs a round trip rather than a re-download and a changed one shows through at once. The explorer's vendored libraries lose the long cache too: their names carry no version either, so they cannot be immutable safely. That is roughly 380 KB revalidated per first visit on a low-traffic public page.backend/tests/test_nginx_static_cache.py: on the rendered config, immutable is confined to/assets/and the catch-all follows it in every SPA vhost. nginx takes the first regex location that matches, so a catch-all placed first would swallow/assets/and drop the week. The rendering fixture moves totests/nginx_helpers.pyso this andtest_nginx_rewrite_ordering.pyshare it.deploy/staging-smoke-test.sh: three live-header probes in the shared-config section, via a newcheck_header_valuehelper: dashtheme-boot.jsand dataapp.cssmust sayno-cache, and one hashed map asset (found from the page) must sayimmutable.data-explorer/README.mdstates the policy its files are served under.Verified
pytest tests/test_nginx_static_cache.py tests/test_nginx_rewrite_ordering.py tests/test_towers_vhost_coverage.py: 10 passed.nginx -ton it innginx:alpine: syntax ok. The data vhost renders= /index.html, then the/assets/location, then the catch-all, in that order.pre-commit run --all-filesclean;bash -nand shellcheck on the smoke script (info-level notes only, matching the existing helpers).dash theme-boot.js revalidates, which is the check doing its job against today's config;data app.css revalidateswarns locally because the DNS guard usesgetent, which macOS lacks, and passes in CI.hashed /assets/ file is immutablepasses.The staging chain never runs on a PR, so the smoke probes are first exercised by the merge run.
ClickUp Edge keeps unhashed static files for a week, so a deploy of the data explorer is invisible until purged.
🤖 Generated with Claude Code