Website still shows old CSS or JavaScript after a deploy (even after a hard refresh)
You changed the stylesheet, deployed, and the page looks exactly the same. A hard refresh fixes it for you, but not for everyone else. Somewhere between the server and the screen, a saved copy is still being served.
Find which cache has the old file
# 1. Ask the server directly, skipping your browser
curl -sI https://example.com/style.css | grep -iE 'cache-control|etag|last-modified|age|cf-cache-status|x-cache'
# 2. Compare what the server sends with what you deployed
curl -s https://example.com/style.css | sha256sum
sha256sum dist/style.css
- Hashes match, page still old: your browser (or a service worker) is the culprit.
- Hashes differ and the response says
HITor has a largeage: a CDN or reverse proxy is serving its saved copy. Purge that URL, or wait for its max-age. - Hashes differ and it is a
MISS: the origin itself still has the old file. The deploy did not copy it, the app server needs a restart, or you are looking at the wrong server.
You can hash both versions in the hash generator if you do not have sha256sum to hand.
The browser cache
Browsers keep a copy of each file for as long as Cache-Control: max-age (or a heuristic based on Last-Modified) allows, and while it is “fresh” they do not even ask the server. A normal reload can still reuse it; a hard reload (Ctrl+Shift+R, Cmd+Shift+R on a Mac) asks again. With DevTools open, tick Disable cache on the Network tab while you work. For an interactive picture of fresh copies, hard refresh and 304 replies, see the browser-cache lesson on Wirebook.
A CDN or reverse proxy
Cloudflare, Fastly, CloudFront, Varnish and nginx proxy_cache all keep their own copies, often for longer than the browser. Purging fixes it today; it will happen again on the next deploy unless the URL changes. On Cloudflare, static file types are cached by default even if your origin sends a short max-age, depending on your cache rules and “Browser Cache TTL” setting.
A service worker
If the site was ever a PWA, a service worker may answer every request from its own cache before the network is touched, so hard refresh does not help on the next normal visit. Open DevTools → Application → Service workers and tick Update on reload (or Unregister). In the worker, bump the cache name on each release and delete old caches in the activate event, or users stay on the old version until every tab is closed.
The permanent fix: new URL per version
<!-- HTML: short cache, always revalidated -->
Cache-Control: no-cache
<link rel="stylesheet" href="/assets/app.3f9a1c7e.css">
<script src="/assets/app.b81d02aa.js" defer></script>
# Hashed assets: safe to keep for a year, they never change
Cache-Control: public, max-age=31536000, immutable
Vite, webpack, Parcel, Next.js and Rails do this for you in production builds. With hand-written sites, append a short content hash as a query string (style.css?v=3f9a1c7e) generated at build time; a hash beats a version number because it changes only when the file does. Do not rely on a random value per request: that turns the cache off entirely.
Still old after all of that?
- The HTML itself is cached, so it still points at the old hashed file. Make HTML
no-cache. - An
@importinside the CSS points at an un-hashed file. - CSS specificity: the new rule is there but another rule wins. Check the Styles pane in DevTools.
- You edited the source but the page loads the built file from a different folder.
Related
no-cache vs no-store · HTTP 304 Not Modified · All Cloudflare error codes · Hash generator · Hash API
Questions
Why does my website still show the old CSS?
A cache (your browser, a CDN, a proxy or a service worker) still has the old file saved under the same URL and is serving it instead of asking the server.
Why does hard refresh fix it only for me?
Hard refresh skips your own browser cache. Other visitors still have their copies, and a CDN copy is unaffected, so the real fix is changing the file URL for each version.
What is cache busting?
Giving every new version of a file a new URL, usually by putting a hash of its contents in the filename or query string, so caches treat it as a brand new file.
Is ?v=123 on a CSS link enough?
Usually yes, as long as the value changes whenever the file changes and the HTML that contains it is not itself cached for long. A content hash is better than a manual number.
How do I check whether Cloudflare is caching a file?
Request it with curl -sI and look for the cf-cache-status header: HIT means it came from Cloudflare’s cache, MISS or DYNAMIC means it came from your server.
Published .