How the HTTP cache decides to reuse a response
The browser stores responses according to Cache-Control headers sent by the server. A directive such as max-age=31536000 tells the browser the resource is fresh for a year, so subsequent requests never reach the network. When max-age expires, the response becomes stale but is not discarded immediately.
Stale responses can still be reused after validation. The browser sends a conditional request containing If-None-Match or If-Modified-Since. If the server replies 304 Not Modified, the cached copy is treated as fresh again. This mechanism keeps bandwidth low but can leave users looking at old content until the validator changes.
- no-cache forces revalidation on every use even when the response is still fresh
- no-store prevents any storage in the HTTP cache or intermediate proxies
- must-revalidate requires a successful validation before a stale response is served
Validators that make revalidation efficient
ETag and Last-Modified headers let the server identify whether a resource has changed without sending the full body. An ETag is usually a hash of the content; Last-Modified records the timestamp of the last change. When either value matches what the browser already holds, the server can safely return 304.
Developers who omit these headers force the browser to download the entire resource on every validation. Adding both ETag and Last-Modified is the minimum needed for efficient cache reuse while still allowing updates to reach users.
- Combine ETag with Cache-Control: no-cache when content must stay reasonably fresh
- Use immutable together with a long max-age on versioned assets to skip reload-time revalidation
- Versioned URLs plus long max-age avoid unnecessary validation requests entirely
Service workers add another caching layer
A service worker sits between the browser and the network. It can intercept every request and apply its own caching strategy before the HTTP cache is consulted. When a service worker returns a cached response, the browser never sees the original Cache-Control headers from the server.
Strategies such as stale-while-revalidate let the service worker serve an old copy immediately while it fetches an update in the background. This improves perceived speed but can keep pages visually stale until the next navigation or manual update. Navigation requests for HTML should normally use Cache-Control: no-cache so the document itself stays fresh.
- Check the Application panel in DevTools to see which resources a service worker currently controls
- Unregister the service worker temporarily to test whether the HTTP cache alone is the source of staleness
- Update the service-worker script itself with max-age=0 or rely on the browser's default update check that ignores HTTP cache since Chrome 68
Reload actions and what they actually send
A normal reload sends Cache-Control: max-age=0 plus If-None-Match and If-Modified-Since. This triggers validation but still allows a 304 response. Force reload (Ctrl+Shift+R or equivalent) adds stricter bypass behavior that most browsers implement by ignoring the HTTP cache for that request.
In JavaScript, fetch with cache mode set to no-cache reproduces the validation behavior of a reload. The reload mode itself is not the correct choice for forcing fresh content; no-cache is the explicit directive that guarantees revalidation.
- Hard reload bypasses the HTTP cache but may still be answered from a service-worker cache
- Clearing storage in DevTools removes both HTTP cache entries and service-worker caches for the origin
- Closing the tab and reopening does not guarantee a fresh load if the service worker remains active
Practical steps when content refuses to update
Start by inspecting the response headers in the Network panel. Look for Cache-Control, ETag, and Last-Modified values. If a long max-age is present on unversioned assets, the page will stay stale until the cache expires or a validator forces an update.
For development, serve HTML with Cache-Control: no-cache and ensure CSS and JavaScript files carry version hashes in their URLs. This combination keeps the document fresh while allowing long-lived caching of immutable assets. When a service worker is involved, add an update mechanism that calls skipWaiting after a new version is installed.
- Disable the service worker in DevTools to isolate whether the HTTP cache or the worker is responsible
- Append a query string with a build hash only when the file content actually changes
- Document the exact reload sequence that reproduces the stale state so others can verify the fix