Cache-Control for committed static art: never immutable
Committed, non-fingerprinted art should be cached with stale-while-revalidate, never immutable — a regenerated asset at the same path must still refresh.
The trap
Cache-Control: immutable is the right answer for fingerprinted build assets: app.3f9a2.js never changes, so cache it forever. It is the wrong answer for art you commit at a stable path. If /images/article-cover.png is immutable and you regenerate it, browsers that saw the old one keep it indefinitely — there is no hash in the URL to bust.
What this site sends
The non-fingerprinted art under /images and /logos gets a middle path instead: cache for a day, then serve stale while revalidating for a week.
{
source: "/:dir(images|logos)/:path*",
headers: [{
key: "Cache-Control",
value: "public, max-age=86400, stale-while-revalidate=604800",
}],
}
Why stale-while-revalidate
It is the best of both. Within the day, the asset is served straight from cache — fast, no request. After it, the browser serves the stale copy and revalidates in the background, so a regenerated image refreshes on the next visit without ever blocking a paint on a network round-trip. immutable gives you the speed and never the refresh.
The rule
Fingerprint in the filename, then immutable. Stable path, then stale-while-revalidate. The mistake is reaching for immutable because it sounds the most aggressive — aggressively caching a mutable URL is just a bug with a long TTL.