How to Generate Cache-Control Headers for Any Stack
Use public, max-age=31536000, immutable for hashed static assets. Use no-cache for HTML pages. Use no-store for authenticated API responses. The Cache-Control Generator builds the exact header and stack config for each case.
Cache-Control is the most misunderstood HTTP header. The right value depends on three things: what the resource is, who can cache it, and how long it stays valid. Get it wrong and users get stale files or every request hits your origin.
Generate Cache-Control headers →The four patterns you actually need
1. Static assets with hashed filenames
JavaScript, CSS, fonts, and images with fingerprinted filenames (e.g. app.a3f9d2c.js) — cache forever. The filename changes when content changes, so the browser never gets stale files.
Cache-Control: public, max-age=31536000, immutable
# Nginx
location ~* \.(js|css|woff2|woff|ttf|png|jpg|gif|ico|svg)$ { add_header Cache-Control "public, max-age=31536000, immutable" always; etag off; expires 1y;
}
2. HTML pages
HTML should always be revalidated so users get the latest asset URLs. no-cache stores the response but checks for updates on every request — no latency unless the page actually changed.
Cache-Control: no-cache
# Nginx
location ~* \.html$ { add_header Cache-Control "no-cache" always;
}
3. Public API responses
API responses that don't contain user-specific data can be cached at the CDN layer. stale-while-revalidate serves from cache immediately while refreshing in the background.
Cache-Control: public, max-age=60, stale-while-revalidate=300
# Nginx
location /api/public/ { add_header Cache-Control "public, max-age=60, stale-while-revalidate=300" always; add_header Vary "Accept-Encoding" always;
}
4. Authenticated API responses
Any response containing user data must never be stored by CDNs or shared caches. no-store prevents storage entirely — every request goes to origin.
Cache-Control: no-store
# Nginx
location /api/ { add_header Cache-Control "no-store" always; add_header Pragma "no-cache" always;
}
Vercel — vercel.json
{ "headers": [ { "source": "/static/(.*)", "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] }, { "source": "/(.*).html", "headers": [{ "key": "Cache-Control", "value": "no-cache" }] }, { "source": "/api/(.*)", "headers": [{ "key": "Cache-Control", "value": "no-store" }] } ]
}
Cloudflare Transform Rules
Dashboard → Rules → Transform Rules → Modify Response Header. Set static values per path pattern. Cloudflare rules override origin headers when both are set.
The two directives most people get wrong
| Directive | What it actually means |
|---|---|
no-cache | NOT "don't cache" — stores the response but revalidates before every use |
no-store | Never store anywhere — this is the "don't cache" directive |
immutable | Skip revalidation on page refresh — only safe with hashed filenames |
must-revalidate | Once stale, must check origin — returns 504 if origin unreachable |
immutable without hashed filenames. If the filename doesn't change when content changes, browsers will serve the old version permanently until the max-age expires.