stale-while-revalidate and Cache-Control immutable: what Chrome actually does (tested)

Both directives are about avoiding a wait on revalidation. We made revalidation deliberately slow (2 s) and timed what the browser did.

1. stale-while-revalidate: measured

Cache-Control on the scriptVisit 2.5 s later (cache expired)Script version that ranNext visit
max-age=12,024 ms: page waited for the revalidationnew (v2)waited again, v3
max-age=1, stale-while-revalidate=6020 ms: stale copy used at onceold (v1)v2, fetched in the background

That is the whole trade: within the stale-while-revalidate window the visitor gets the old file instantly and the browser refreshes it in the background, so the next visit gets the new one. Users can see content one version behind; use it for things where that's fine (avatars, API lists, CSS/JS with stable URLs), not for prices or auth state.

Cache-Control: max-age=60, stale-while-revalidate=86400

# nginx
location /api/feed { add_header Cache-Control "max-age=60, stale-while-revalidate=86400" always; proxy_pass http://app; }

After max-age + stale-while-revalidate seconds the copy is simply expired and the next visit waits again.

2. Cache-Control: immutable: no difference in Chrome

Cache-Control on the scriptRevisitReload
max-age=600from cache, no requestfrom cache, no request
max-age=600, immutablefrom cache, no requestfrom cache, no request

immutable means "this URL's content will never change, don't revalidate even on reload". Chrome already doesn't revalidate fresh subresources on a normal reload (only the page itself), so the header changed nothing in our test. It is harmless and helps browsers that do revalidate on reload; send it only on fingerprinted URLs (app.3f9a1c.js), never on /app.js:

# quotes required: an unquoted {8,} is a config error ("unknown directive")
location ~* "\.[0-9a-f]{8,}\.(js|css|woff2)$" {
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

Background: no-cache vs no-store, measured and how ETag revalidation works.

Frequently asked questions

What does stale-while-revalidate mean?

After max-age expires, the browser may use the stale copy for that many more seconds while it fetches a fresh one in the background. In our Chrome test the page loaded in 20 ms with the old script instead of waiting 2 s, and the next visit got the new one.

Does Chrome support stale-while-revalidate?

Yes, in the browser HTTP cache: Chromium 153 served the stale script immediately and revalidated in the background.

What does Cache-Control immutable mean?

That the content at this URL never changes, so the browser shouldn't revalidate it even on reload. Use it only on fingerprinted file names.

Does immutable do anything in Chrome?

Not in our test: max-age=600 and max-age=600, immutable both stayed in cache with no request on revisit and on reload.

Is stale-while-revalidate the same as SWR in Next.js?

Same idea (show cached data, refresh in the background); the HTTP directive does it in the browser or CDN cache, the SWR library in your JavaScript.

Written with AI assistance. Measured on 2026-10-01 in Chromium 153 (full build), fresh context per row: a page loaded a script whose Cache-Control we varied; the server logged every request and answered conditional (If-None-Match) requests after a 2-second delay with a new version, so we could see whether the page waited and which version ran. Reload behaviour was checked with page.reload(). Both nginx snippets were checked with nginx -t and wget in nginx 1.31.6 (fingerprinted file got the immutable header, /app.js didn't). CDN (Cloudflare, CloudFront) and Firefox behaviour are not covered by this test. PlainHeaders is our extension; the other methods are free.