notes
Feb 27, 2025 · 1 min read

Cache Headers, Finally Explained to Myself

For a long time my entire cache strategy was “Cloudflare probably handles this,” which is true in the sense that Cloudflare has sensible defaults, and false in the sense that I had never actually looked at what those defaults were doing to my own build output.

Static assets from an Astro build get content hashes in their filenames — app.a3f92c.css, not app.css — which means they never change once built. That single fact should mean they’re cacheable forever, but the default headers Cloudflare Pages was sending only cached them for a few hours, the same as the HTML.

The fix was a _headers file, which is about as low-ceremony as configuration gets:

/assets/*
  Cache-Control: public, max-age=31536000, immutable

HTML pages still get short-lived caching, since those can change on every deploy without a filename change to signal it. Hashed assets get a year and the immutable flag, which tells the browser not to even bother revalidating. Writing that one file out forced me to actually explain to myself, in writing, why the two categories of file need different rules — which is the kind of understanding “Cloudflare probably handles this” was quietly letting me skip.