Du deployest, aber Nutzer sehen weiter das alte CSS/JS? Willkommen in der Cache-Hölle. Hier ist der Reality-Check: wo es cached, wie du’s findest, und wie du’s dauerhaft sauber machst.
„Hab doch deployed!“ – und trotzdem: Styles sind alt, JS ist alt, Bilder sind alt. Du machst Hard Reload, es ist weg. Nutzer schreiben weiter: „Bei mir ist’s kaputt.“
Das ist kein Bug, das ist Cache. Und zwar nicht „der Cache“, sondern oft gleich mehrere: Browser, CDN, Reverse Proxy, Service Worker, manchmal sogar Zwischen-Caches.
Gute Nachricht: Du kannst das komplett in den Griff bekommen. Schlechte Nachricht: Mit „Purge CDN und hoffen“ baust du dir meistens nur eine neue Runde Hölle.
- 90% Ursache: Assets ohne Fingerprint/Hash + zu lange Cache-Header.
- Wichtigste Regel: HTML kurz cachen, gehashte Assets lang cachen.
- Service Worker checken: Der kann deine App „einfrieren“, selbst wenn CDN/Browserscache ok ist.
- Debug zuerst: Finde den Cache-Ort (Browser vs CDN vs SW), dann fixen.
- Saubere Lösung: Content-Hash im Dateinamen + deployt neue HTML-Referenzen + optional CDN-Purge nur fürs HTML.
Wo wird überhaupt gecacht? (Spoiler: überall)
Browser Cache
Der Browser cached Assets basierend auf Cache-Control/ETag/Last-Modified. Wenn du dieselbe URL wiederverwendest (z.B. app.css), ist die Wahrscheinlichkeit hoch, dass irgendwer noch die alte Version hat.
CDN / Reverse Proxy
CDNs cachen oft aggressiver als du denkst (und mit eigenen Regeln: s-maxage, Edge-TTL, Cache Keys, „Cache Everything“). Wenn du HTML am Edge cached, referenziert dein HTML vielleicht noch alte Assets.
Service Worker
Service Worker sind der „Cache-Boss“. Wenn du PWA/Offline-Cache nutzt (oder ein Framework dir das eingebaut hat), kann der SW Requests abfangen und aus seinem Cache bedienen – unabhängig von Browser/CDN.
Die häufigsten Ursachen (und warum sie so fies sind)
1) Du überschreibst Assets unter derselben URL
app.css bleibt app.css. Du deployest neue Inhalte, aber Browser/CDN sehen: „kenne ich“. Ergebnis: alte Version bleibt, bis TTL abläuft oder revalidated wird.
- Fix: Asset Fingerprinting (Hash im Dateinamen):
app.4f3a1c.cssstattapp.css. - Fix: Lange Cache-Zeiten nur für gehashte Assets.
2) HTML wird zu lange gecacht (und zeigt auf alte Assets)
Selbst wenn deine Assets perfekt gehasht sind: Wenn Nutzer noch ein altes HTML aus dem Cache bekommen, referenziert das HTML die alten Hash-Dateien.
- Fix: HTML kurz cachen und revalidieren lassen (statt monatelang).
- Fix: CDN-Regel: HTML nicht „Cache Everything“ ohne Plan.
3) Service Worker liefert alte Bundles aus
SW kann „stale“ Assets ausliefern, bis ein Update sauber durchläuft. Das sieht dann aus wie: „Nur bei manchen Usern alt – und die bleiben alt.“
- Fix: SW-Update-Flow sauber: neue Version erkennen, Nutzer informieren/refresh ermöglichen.
- Fix: Caching-Strategie prüfen (z.B. nicht „cache-first“ für HTML ohne Update-Mechanik).
4) CDN Cache-Key ist falsch (Query-Strings, Cookies, Vary)
Wenn dein CDN Cache-Key Dinge ignoriert oder zu viel unterscheidet, kriegst du entweder „falsche Version“ oder „Cache hitzt nie“. Beides ist schlecht.
- Fix: Cache-Key bewusst definieren: meist URL + relevante Header, aber nicht „alles“.
- Fix:
Varysparsam und bewusst einsetzen (sonst Cache-Split).
5) Revalidation missverstanden: ETag/304 hilft nicht, wenn du falsche URLs nutzt
ETag/Last-Modified sind gut, aber sie sind keine Wunderwaffe gegen „gleiche URL, anderer Inhalt“. Du willst trotzdem versionierte Asset-URLs, sonst sind einzelne Clients immer wieder „hinterher“.
Symptom → wahrscheinlichster Cache-Ort → Quick Fix
| Symptom | Wo cached es vermutlich? | Quick Fix |
|---|---|---|
| Hard Reload bei dir hilft, Nutzer sehen weiter alt | CDN/Proxy oder Service Worker | SW prüfen; CDN HTML kurz cachen; Asset-URLs versionieren |
| Nur CSS ist alt, JS ist neu | Browser Cache (CSS) oder CDN Edge TTL | CSS fingerprinten; Cache-Control für gehashte Assets lang |
| Nur bestimmte Länder/Netze sehen alt | CDN PoP / ISP Cache / Edge | Purge gezielt (HTML), Cache-Key prüfen |
| SPA zeigt alte UI, bis Tab geschlossen wird | Service Worker oder aggressives App-Shell Caching | Update-Flow + Refresh-Mechanik, HTML nicht cache-first |
| Bilder bleiben alt trotz Austausch | Browser/CDN Asset Cache | Bild-URL versionieren (Hash oder neuer Pfad), nicht überschreiben |
So setzt du’s um
So setzt du’s um: Debug-Runbook (15–30 Minuten)
- Reproduzieren: Betroffene URL öffnen, DevTools → Network → „Disable cache“ (nur für DevTools) aktivieren.
- Headers prüfen: Response Headers für HTML und für CSS/JS anschauen:
Cache-Control,ETag,Age, ggf. CDN-Header (z.B. „cache status“). - 304/200 verstehen: Kommt 304? Dann arbeitet Revalidation. Kommt 200 aus „disk cache“/„memory cache“? Dann Browsercache. Kommt 200 mit CDN-Hit? Dann Edge.
- Service Worker prüfen: DevTools → Application → Service Workers: aktiv? „Bypass for network“ testen. Wenn’s dann plötzlich „neu“ ist: SW ist dein Schuldiger.
- Asset-URLs checken: Referenziert HTML wirklich die neue Datei (Hash)? Wenn HTML alt ist, siehst du hier sofort die alte Referenz.
Saubere Lösung (die nicht jede Woche wiederkommt)
- Fingerprints für Assets: CSS/JS/Bilder, die du deployest, bekommen Hash im Dateinamen.
- Cache-Regel: Gehashte Assets: lange Cache-Zeit (optional „immutable“), weil URL sich bei Änderung ändert.
- HTML-Regel: HTML kurz cachen oder revalidieren (damit neue Asset-Referenzen schnell ankommen).
- CDN Purge nur gezielt: wenn nötig, dann eher HTML/Entry-Points purgen, nicht „alles“.
- Service Worker Update-Flow: neue SW-Version erkennen, Nutzer kontrolliert refreshen lassen.
Merksatz:
HTML kurz & revalidate.
Assets mit Hash lang & immutable. Typische Fehler & wie du sie vermeidest
- Fehler: „Cache-Control: max-age=31536000“ für alles, inkl. HTML. Fix: HTML separat behandeln (kurz/revalidate), Assets mit Hash lang.
- Fehler: Assets werden überschrieben, URL bleibt gleich. Fix: Hash/Fingerprinting, nicht überschreiben.
- Fehler: Service Worker wird einmal eingebaut und nie wieder angefasst. Fix: Update-Strategie + klare Ownership.
- Fehler: „Purge All“ als Standard. Fix: gezielt purgen (Entry-Points) und Versioning korrekt machen.
- Fehler: Query-String Cache Busting wild gemischt. Fix: Hash im Dateinamen ist stabiler und CDN-freundlicher.
Fazit: Cache ist nicht dein Feind – fehlende Strategie ist es
Cache-Hölle entsteht fast immer durch dieselbe Kombi: gleiche Asset-URLs + lange TTLs + HTML zu aggressiv gecacht (und manchmal ein Service Worker obendrauf).
Nächster Schritt: Implementiere Fingerprinting für Assets und trenne Cache-Regeln strikt: HTML kurz/revalidate, gehashte Assets lang/immutable. Danach brauchst du „Purge All“ eigentlich nie wieder.
FAQ
Warum hilft Hard Reload nur bei mir, aber nicht bei Nutzern?
Weil du damit primär deinen Browsercache umgehst. Wenn das alte Asset im CDN/Proxy oder im Service Worker steckt, bleibt es für andere Nutzer trotzdem alt.
Was ist der Unterschied zwischen max-age und s-maxage?
max-age ist die Cache-Zeit für Clients (Browser). s-maxage ist für Shared Caches (z.B. CDNs/Proxies) und kann abweichend sein. Das ist genau der Grund, warum „bei mir neu“ und „für Nutzer alt“ gleichzeitig passieren kann.
Warum ist „Asset Fingerprinting“ die beste Lösung?
Weil sich bei jeder Änderung die URL ändert. Cache darf dann aggressiv sein, ohne dass alte Versionen „aus Versehen“ weiterleben. Du musst nicht raten, ob irgendwo noch eine alte Datei hängt.
Was bedeutet stale-while-revalidate?
Der Cache darf kurz eine ältere Antwort ausliefern („stale“), während im Hintergrund eine neue Version geholt wird („revalidate“). Das kann Performance verbessern, ist aber bei Entry-HTML nur dann sinnvoll, wenn du Versioning sauber im Griff hast.
Woran erkenne ich, dass ein Service Worker schuld ist?
In DevTools (Application → Service Workers) siehst du, ob einer aktiv ist. Wenn „Bypass for network“ plötzlich das Problem löst oder Nutzer „ewig alt“ bleiben, ist der SW sehr wahrscheinlich der Grund.
Muss ich immer CDN purgen?
Wenn du Fingerprinting sauber machst, musst du gehashte Assets praktisch nie purgen. Wenn überhaupt, purgst du gezielt Entry-Points/HTML, damit neue Referenzen schneller überall ankommen.