Cache Busting Strategies — How to Invalidate Browser and CDN Cache
The most reliable cache busting strategy is filename hashing — include a content hash in the filename (e.g. app.a3f9d2c.js). The hash changes when content changes, forcing a new cache entry. Query strings (?v=2) work but are less reliable. Cache-Control: no-cache on HTML + immutable on hashed assets is the standard pattern.
Cache busting forces browsers and CDNs to fetch a fresh copy of a resource even when the previous copy hasn't expired. Getting it wrong means users see stale files after a deploy.
Strategy 1 — Filename hashing (recommended)
Include a content hash in the filename. Every build tool supports this.
# Webpack / Vite — output filenames include content hash
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
}
# Result: app.a3f9d2c.js → app.b8d4e1a.js when content changes
With hashed filenames, set max-age=31536000, immutable — the hash is the version. Browsers never need to revalidate.
Cache-Control: public, max-age=31536000, immutable
Strategy 2 — HTML with no-cache
HTML should never be cached immutably. It references your asset filenames. If the HTML is stale, browsers load the old JS/CSS even if the new files are deployed.
Cache-Control: no-cache # no-cache stores the response but revalidates before serving # Browser checks: "has the HTML changed?" before using cached version # If unchanged: serves from cache (304 Not Modified, no data transfer) # If changed: fetches fresh copy
Strategy 3 — Query string versioning
Less reliable — some CDNs ignore query strings by default. Use only when you can't control filenames.
<script src="/app.js?v=1.2.3"></script> <link rel="stylesheet" href="/style.css?v=1.2.3">
Strategy 4 — CDN cache purge
# Cloudflare — purge specific URLs
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" -H "Authorization: Bearer API_TOKEN" -H "Content-Type: application/json" --data '{"files":["https://example.com/app.js"]}'
# Purge everything
--data '{"purge_everything":true}'
# Vercel — automatic on every deployment
# No manual purge needed — new deployment = new CDN cache
Strategy 5 — stale-while-revalidate
Not strictly cache busting, but allows zero-latency deploys. Serve stale content immediately while fetching fresh in background.
Cache-Control: public, max-age=60, stale-while-revalidate=300 # User gets immediate response (stale) # Background: fresh copy fetched # Next request: serves the fresh copy
The standard pattern — all together
| Resource | Cache-Control | Why |
|---|---|---|
| HTML | no-cache | Always revalidate — references asset filenames |
| JS/CSS (hashed) | public, max-age=31536000, immutable | Hash = version. Never expires manually. |
| Images (hashed) | public, max-age=31536000, immutable | Same as above |
| Images (unhashed) | public, max-age=86400 | 1 day — balance freshness vs performance |
| API (public) | public, max-age=60, stale-while-revalidate=300 | Short cache, background refresh |
| API (auth) | no-store | Never store anywhere |