Was Sie niemals cachen sollten
Caching ist eines der wirkungsvollsten Werkzeuge für die Frontend-Performance. Richtig eingesetzt, eliminiert es redundante Netzwerkanfragen, reduziert die Serverlast und lässt Ihre Anwendung sofort reagieren. Falsch eingesetzt, gibt es private Daten preis, liefert veraltete authentifizierte Inhalte aus oder sperrt Benutzer in fehlerhafte Seitenzustände, aus denen sie nicht entkommen können.
Die Regel ist einfacher, als die meisten Artikel vermuten lassen: Statische, versionierte, öffentliche Assets können fast immer bedenkenlos aggressiv gecacht werden. Alles andere sollte standardmäßig als unsicher betrachtet werden.
Bevor wir uns damit befassen, was nicht gecacht werden sollte, ist es hilfreich, genau zu verstehen, wo Caching stattfindet – denn diese Ebenen verhalten sich sehr unterschiedlich.
Wichtigste Erkenntnisse
- Cache-Ebenen (HTTP, CDN, Service Worker, bfcache, Web Storage) haben unterschiedliche Geltungsbereiche und Sicherheitseigenschaften; sie gleichzusetzen verursacht reale Fehler.
no-storeverhindert die Speicherung vollständig, währendno-cachelediglich eine Revalidierung vor der Wiederverwendung erzwingt.- Authentifizierte oder benutzerspezifische Antworten müssen
Cache-Control: private, no-storeverwenden, um zu verhindern, dass gemeinsam genutzte Caches Daten zwischen Benutzern weitergeben. - Speichern Sie niemals JWTs, Refresh-Tokens oder API-Schlüssel in
localStorageoderIndexedDB; bevorzugen Sie stattdessenHttpOnly-Cookies. - Verwenden Sie standardmäßig
no-store, wenn eine veraltete oder gemeinsam genutzte Kopie ein Sicherheits- oder Korrektheitsproblem verursachen könnte.
Die Cache-Ebenen sind nicht austauschbar
Die meisten Caching-Fehler beginnen damit, diese Ebenen als gleichwertig zu behandeln:
- HTTP/Browser-Cache — Gesteuert durch
Cache-Control-Header gemäß RFC 9111. Wird zwischen Tabs geteilt und bleibt sitzungsübergreifend erhalten. - CDN/Shared Cache — Befindet sich zwischen Ihren Benutzern und Ihrem Origin-Server. Cloudflare CDN und ähnliche Dienste cachen Antworten für alle Benutzer, nicht nur für einen einzelnen.
- Service Worker Cache API — Ein programmierbarer Cache, den Sie vollständig in JavaScript kontrollieren. Bleibt erhalten, bis er explizit geleert wird.
- bfcache (Back/Forward Cache) — Der In-Memory-Snapshot einer vollständigen Seite im Browser, der beim Vor- und Zurücknavigieren verwendet wird. Vom HTTP-Cache getrennt.
localStorage/sessionStorage— Key-Value-Speicher. Keine integrierte Ablaufverwaltung, vollständig über JavaScript zugänglich.IndexedDB— Strukturierter persistenter Speicher. Dieselbe XSS-Angriffsfläche wielocalStorage.
Jede Ebene hat unterschiedliche Persistenz-, Geltungsbereichs- und Sicherheitseigenschaften. Sie zu verwechseln führt zu realen Fehlern.
Ein kurzer Hinweis zur Cache-Control-Semantik
Diese Unterscheidung wird häufig falsch dargestellt, daher ist es wichtig, sie klar zu formulieren:
no-storebedeutet, dass der Cache die Antwort überhaupt nicht speichern darf.no-cachebedeutet nicht „nicht cachen”. Es bedeutet, dass die gecachte Kopie vor der Wiederverwendung beim Server revalidiert werden muss.
Wenn Sie verhindern möchten, dass eine Antwort irgendwo gespeichert wird, verwenden Sie no-store. Wenn Sie bei jeder Verwendung Aktualität garantieren, aber dennoch von der Effizienz bedingter Anfragen (ETags, 304s) profitieren möchten, verwenden Sie no-cache.
Was Sie niemals cachen sollten
Authentifizierte API-Antworten und benutzerspezifisches HTML
Jede Antwort, die sich je nach Benutzeridentität unterscheidet – Dashboard-HTML, Kontoseiten, API-Antworten mit Profildaten – sollte nicht in einem Shared Cache gespeichert werden. Ein gängiges Muster ist:
Cache-Control: private, no-store
no-store verhindert, dass normale HTTP-Caches die Antwort speichern, während private gemeinsam genutzten Caches wie CDNs explizit mitteilt, die Antwort nicht für mehrere Benutzer zu cachen.
Ohne private kann ein CDN wie Cloudflare eine Antwort, die für Benutzer A zurückgegeben wurde, cachen und sie Benutzer B ausliefern. Dies hat in der Praxis bereits zu realen Datenpannen geführt.
Alles innerhalb eines Service Workers, das Authentifizierung erfordert
Die Service Worker Cache API ist im Wesentlichen ein programmierbarer Netzwerk-Proxy. Ein Service Worker fängt jede Fetch-Anfrage in seinem Geltungsbereich ab. Wenn Sie authentifizierte API-Antworten oder benutzerspezifisches HTML darin blind cachen, bleiben diese Daten im Cache-API-Speicher erhalten – möglicherweise sitzungsübergreifend und für den Service Worker unabhängig vom Anmeldestatus zugänglich.
// ❌ Don't do this
self.addEventListener('fetch', event => {
event.respondWith(
fetch(event.request).then(response => {
return caches.open('v1').then(cache => {
cache.put(event.request, response.clone());
return response;
});
})
);
});
Dieses Muster cached alles unterschiedslos. Schließen Sie authentifizierte Routen explizit aus:
// ✅ Only cache public, versioned assets
const CACHEABLE = ['/shell.html', '/app-abc123.js', '/styles-f9c.css'];
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (url.origin !== self.location.origin) return; // ignore cross-origin
if (!CACHEABLE.includes(url.pathname)) return; // let it go to network
event.respondWith(
caches.match(event.request).then(r => r || fetch(event.request))
);
});
Geheimnisse in localStorage oder IndexedDB
Das OWASP HTML5 Security Cheat Sheet ist eindeutig: Das Speichern von JWTs, Refresh-Tokens, API-Schlüsseln oder Passwort-Reset-Tokens in localStorage oder IndexedDB ist riskant, da jede XSS-Schwachstelle auf Ihrer Domain diese auslesen kann. Diese Speichermechanismen haben keine integrierte Ablaufverwaltung, kein HttpOnly-Äquivalent und keine Isolation gegenüber eingeschleusten Skripten.
Verwenden Sie nach Möglichkeit HttpOnly-, Secure- und SameSite-Cookies für Tokens. Wenn Sie localStorage verwenden müssen, sollten Sie sich bewusst sein, dass Sie dieses Risiko bewusst eingehen.
Discover how at OpenReplay.com.
Benutzerspezifische Antworten auf CDN-Ebene
Ein häufiger Fehler bei Cloudflare CDN: Das Vergessen der private-Direktive bei Antworten, die Benutzerdaten enthalten, oder eine fehlerhafte Vary-Konfiguration.
# ❌ Missing private — CDN may cache this for all users
Cache-Control: max-age=300
Content-Type: application/json
# ✅ Correct
Cache-Control: private, no-store
Wenn Ihre Antwort legitim je nach einem Request-Header variiert (wie Accept-Language oder Authorization), müssen Sie einen korrekten Vary-Header angeben. Ohne diesen könnte ein CDN eine gecachte französischsprachige Antwort an einen englischsprachigen Benutzer ausliefern – oder schlimmer noch, eine gecachte authentifizierte Antwort an einen nicht authentifizierten Benutzer.
Achten Sie außerdem auf Cache-Poisoning durch nicht berücksichtigte Parameter: Wenn Ihr CDN auf Basis der URL cacht, Ihre Anwendung jedoch einen nicht validierten Query-Parameter oder Header zur Konstruktion der Antwort verwendet, kann ein Angreifer den Cache durch eine manipulierte Anfrage vergiften.
Seiten, die den bfcache nicht überleben sollten
Chromium-basierte Browser sind in neueren Versionen zunehmend bereit, bfcache auch für Seiten zu verwenden, die Cache-Control: no-store senden. Dieses Verhalten ist browserabhängig und nicht einheitlich über Chrome, Firefox und Safari hinweg. caniuse zeigt eine breite Unterstützung für bfcache in modernen Browsern, aber das Zusammenspiel mit no-store unterscheidet sich je nach Browser-Engine und -Version.
Wenn Sie Seiten haben, bei denen die Wiederherstellung eines veralteten In-Memory-Snapshots tatsächlich unsicher ist – etwa eine Seite, die eine einmalige Zahlungsbestätigung oder einen sitzungssensitiven Zustand anzeigt – verwenden Sie das pageshow-Event, um bfcache-Wiederherstellungen zu erkennen und die Seite neu zu laden oder zu revalidieren:
window.addEventListener('pageshow', event => {
if (event.persisted) {
// Page was restored from bfcache
window.location.reload();
}
});
Gehen Sie nicht davon aus, dass no-store bfcache browserübergreifend universell deaktiviert.
Das grundlegende Denkmodell
Wenn Sie unsicher sind, ob etwas gecacht werden sollte, fragen Sie sich: Könnte das Ausliefern einer veralteten oder gemeinsam genutzten Kopie davon ein Sicherheits- oder Korrektheitsproblem verursachen? Wenn ja, verwenden Sie standardmäßig Cache-Control: no-store und arbeiten Sie sich von dort aus vor.
Statische Assets mit content-gehashten URLs – Ihre JavaScript-Bundles, CSS-Dateien, Bilder – können mit max-age=31536000, immutable ein Jahr lang sicher gecacht werden. Alles, was mit einer Benutzersitzung, einem Authentifizierungsstatus oder einer sensiblen Operation verknüpft ist, hingegen nicht.
Fazit
Aggressives Caching ist ein Feature. Unbeabsichtigtes Cachen der falschen Inhalte ist eine Sicherheitslücke. Die Disziplin liegt darin zu wissen, welche Ebene Sie ansprechen, was jede Cache-Control-Direktive tatsächlich garantiert und welche Datenkategorien niemals die enge Kontrolle des Origin-Servers verlassen sollten. Behandeln Sie alles, was mit einem Benutzer, einer Sitzung oder einem Geheimnis verknüpft ist, standardmäßig als nicht cachebar, und reservieren Sie aggressives Caching für die statischen, versionierten Assets, für die es konzipiert wurde.
Häufig gestellte Fragen
Es ist möglich, wird aber nicht empfohlen. Jede XSS-Schwachstelle auf Ihrer Domain gibt einem Angreifer vollständigen Lesezugriff auf localStorage, was bedeutet, dass er das Token stehlen und den Benutzer imitieren kann. HttpOnly-, Secure- und SameSite-Cookies sind sicherer, da JavaScript sie nicht direkt auslesen kann. Wenn Sie localStorage verwenden müssen, akzeptieren Sie das XSS-Risiko und sollten stark in CSP und Eingabebereinigung investieren.
no-store verbietet normalen HTTP-Caches, die Antwort zur Wiederverwendung zu speichern. no-cache erlaubt das Speichern der Antwort, erfordert jedoch vor jeder Wiederverwendung eine Revalidierung beim Origin-Server, typischerweise über ETag- oder Last-Modified-Bedingungsanfragen. Verwenden Sie no-store für sensible Daten und no-cache, wenn Sie Aktualitätsgarantien benötigen, aber dennoch von 304-Not-Modified-Antworten profitieren möchten.
Meist liegt das daran, dass der Antwort die private-Direktive fehlt oder ein falscher Vary-Header gesetzt ist. Ohne private behandelt ein Shared Cache die Antwort als für alle cachebar. Ohne Vary auf Headers wie Authorization oder Cookie ignoriert das CDN diese Headers bei der Generierung von Cache-Keys und könnte die Antwort von Benutzer A an Benutzer B ausliefern. Senden Sie bei benutzerspezifischen Antworten immer Cache-Control: private, no-store.
Es gibt keinen zuverlässigen browserübergreifenden Header, der bfcache deaktiviert. Cache-Control: no-store funktioniert in einigen Browsern, aber nicht konsistent. Der zuverlässige Ansatz besteht darin, auf das pageshow-Event zu lauschen und event.persisted zu prüfen. Ist dieser Wert true, wurde die Seite aus dem bfcache wiederhergestellt, und Sie können einen Reload erzwingen oder sensible Zustände neu abrufen. Dies ist essenziell für Zahlungsbestätigungen und Seiten nach dem Abmelden.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.