What this changes, and why
Adds public/_headers so Cloudflare Pages sends an explicit Cache-Control per asset class. Goal: returning visitors stay fast and pick up new deploys quickly, without ever needing a manual Cloudflare cache purge.
Context: the root cause of the recurring "site messed up until purge-everything" was Cloudflare APO (a WordPress-era leftover) edge-caching HTML on an Astro/Pages origin — that has been disabled in the Cloudflare dashboard. This MR is the browser-side hardening so stale caching cannot bite returning visitors either.
Per class:
/assets/* — content-hashed by Astro (CSS, JS, fonts under /assets/fonts/) → max-age=31536000, immutable. Filename changes on content change, so no revalidation is ever needed.
/images/* — clean stable filenames; by editor convention a replaced photo gets a NEW filename → max-age=31536000 without immutable, so a hard reload can still revalidate and recover if a filename is ever reused.
/scripts/* — hand-written modules keep the same filename (external references depend on it) but their content changes on deploy → max-age=60, stale-while-revalidate=86400: served instantly from cache, refreshed in the background, at most one navigation behind after a deploy.
Freshness relies on the ETags Pages already sends, so a revalidation is a cheap bodyless 304. HTML is intentionally untouched (already max-age=0, must-revalidate, served fresh from origin).
What this affects
Only HTTP response headers for static assets served by Cloudflare Pages. No application code, no build logic, no filenames change. Nothing security-sensitive (no profile / key export / OpenBunker paths touched). _headers has no effect in local dev — it only applies on Pages (preview + production).
How to try it
On the preview deploy link, check the three classes:
`` curl -sSI <preview-url>/assets/<any-hashed>.css | grep -i cache-control # max-age=31536000, immutable curl -sSI <preview-url>/images/og-default.jpg | grep -i cache-control # max-age=31536000 (no immutable) curl -sSI <preview-url>/scripts/global.js | grep -i cache-control # max-age=60, stale-while-revalidate=86400 ``
Expect each header to match the class above. Also confirm the site still loads and Nostr scripts run (the /scripts/* change is the only one that alters behaviour for returning visitors).