Caching‑Strategien, die in der Praxis wirken
Von Lars Decker
Caching gehört zu den wirkungsvollsten Hebeln für schnelle, stabile Web-Anwendungen – und gleichzeitig zu den Bereichen, in denen kleine Konfigurationsfehler schnell zu schwer nachvollziehbaren Problemen führen. Dieser Leitfaden konzentriert sich auf pragmatische Caching-Patterns, die sich in realen Projekten bewährt haben: ohne Over-Engineering, mit klaren Defaults.
Ziele und Grundbegriffe
- Freshness vs. Validation: Eine gecachte Antwort gilt entweder für einen bestimmten Zeitraum direkt als aktuell, oder sie wird bei Bedarf schnell validiert, etwa über
ETagoderLast-Modified. Ist die Ressource unverändert, reicht eine schlanke304 Not Modified-Antwort. - Public vs. Private: Öffentliche Inhalte lassen sich im Browser und über ein CDN cachen. Personalisierte oder sensible Inhalte gehören nur in den Browser-Cache oder unter gezielter Umgehung gemeinsamer Caches.
- TTL und SWR:
max-agelegt fest, wie lange eine Ressource als gültig gilt.s-maxagegilt speziell für Shared Caches wie CDNs. Mitstale-while-revalidatelassen sich veraltete Inhalte kurzfristig weiter ausliefern, während sie im Hintergrund aktualisiert werden.stale-if-errorerlaubt die Auslieferung einer älteren Version, wenn der Origin vorübergehend nicht erreichbar ist. - Vary und Cache-Key: Der Cache muss wissen, anhand welcher Request-Eigenschaften unterschiedliche Varianten einer Ressource entstehen, etwa
Accept-Encoding,Accept-Languageoder Cookies.
Die wichtigsten Caching-Ebenen
Caching findet an mehreren Stellen statt, und jede Ebene erfüllt einen etwas anderen Zweck.
- Browser-Cache: praktisch kostenlos und direkt beim Nutzer verfügbar. Geeignet für statische Assets wie JavaScript, CSS, Fonts und Bilder.
- CDN- bzw. Edge-Cache: reduziert die Last auf dem Origin und sorgt weltweit für kurze Antwortzeiten. Gut für statische Assets, öffentliche APIs und öffentliches HTML.
- App- bzw. Server-Cache: In-Memory-Caches, LRU-Caches, Redis oder Datenbank-Query-Caches. Schnell, aber nur mit klarer Invalidierungsstrategie.
- Client-State-Cache: Bibliotheken wie SWR oder TanStack Query entkoppeln die Benutzeroberfläche vom Netzwerk und ermöglichen eine kontrollierte Wiederverwendung bereits geladener Daten.
HTTP-Caching: die wichtigsten Direktiven
Cache-Control: public, max-age=31536000, immutable
# Versionierte Assets mit Fingerprint
Cache-Control: public, s-maxage=300, max-age=0, stale-while-revalidate=60
# Öffentliches HTML oder APIs hinter einem CDN
Cache-Control: private, no-store
# Sensible oder stark personalisierte Inhalte
ETag: "abc123"
# Validator für 304-Antworten
Vary: Accept-Encoding, Accept-Language
# Unterschiedliche Varianten derselben RessourceEin paar praktische Regeln dazu:
- Nutze
s-maxage, sobald ein CDN oder ein anderer Shared Cache beteiligt ist. Für diese Caches überschreibt der Wertmax-age. - Verwende
immutablenur für versionierte Assets, etwaapp.7f3c1.js. - Für Inhalte, die sich regelmäßig ändern, aber grundsätzlich cachebar sind, ist eine Validierung über
ETaghäufig sinnvoller als ein kompletter erneuter Download.
Bewährte Startkonfigurationen
1. Statische Assets
Für JavaScript, CSS, Fonts und Bilder mit Fingerprint:
Cache-Control: public, max-age=31536000, immutableVoraussetzung ist konsequentes Fingerprinting über den Dateinamen: Sobald sich der Inhalt ändert, ändert sich auch die URL.
app.7f3c1.js
styles.91ac2.cssDamit lassen sich Assets bedenkenlos sehr lange cachen.
2. Öffentliche HTML-Dokumente
Für dynamisch generiertes, aber öffentliches HTML eignet sich beispielsweise:
Cache-Control: public, s-maxage=300, max-age=0, stale-while-revalidate=60Das CDN darf die Seite fünf Minuten cachen. In einer kurzen Übergangsphase danach liefert es noch die bestehende Version aus, während im Hintergrund bereits eine aktuelle Version geladen wird. Der Browser selbst hält die Seite dagegen praktisch nicht dauerhaft vor.
3. Öffentliche GET-APIs
Für öffentliche oder weitgehend öffentliche API-Endpunkte empfiehlt sich eine Kombination aus CDN-Caching und Validierung:
Cache-Control: public, s-maxage=120, max-age=0, stale-while-revalidate=60
ETag: "abc123"Der Client sendet bei einer späteren Anfrage:
If-None-Match: "abc123"Hat sich der Inhalt nicht geändert, antwortet der Server einfach mit 304 Not Modified. Die TTL kann je nach gewünschter Aktualität etwa zwischen 60 und 300 Sekunden liegen.
4. Authentifizierte Seiten und APIs
Für personalisierte Inhalte ist der sichere Default:
Cache-Control: private, max-age=0Bei besonders sensiblen Daten:
Cache-Control: no-storeEine fortgeschrittene Alternative besteht darin, die öffentliche Grundstruktur einer Seite weiterhin am Edge zu cachen und nur die personalisierten Bestandteile clientseitig nachzuladen. Damit das funktioniert, dürfen Authentifizierungs-Cookies allerdings nicht versehentlich Teil des CDN-Cache-Keys werden.
5. Bilder
Neben dem eigentlichen Caching lohnt sich bei Bildern zusätzlich:
- responsive Varianten über
srcsetundsizes - moderne Formate wie AVIF oder WebP
- serverseitige Limits für Breite und Höhe
- Lazy Loading für Inhalte außerhalb des sichtbaren Bereichs
- Fingerprinting bzw. langfristiges Asset-Caching
Beispiel: Express-/Node.js-Konfiguration
// Fingerprinted Assets
app.use(
"/assets",
express.static("public/assets", {
setHeaders: (res) => {
res.set("Cache-Control", "public, max-age=31536000, immutable");
},
})
);
// Öffentliches HTML
app.get("/", (req, res) => {
res.set(
"Cache-Control",
"public, s-maxage=300, max-age=0, stale-while-revalidate=60"
);
res.send(renderHome());
});
// API mit ETag
app.get("/api/items", (req, res) => {
const body = JSON.stringify(getItems());
const etag =
'W/"' +
require("crypto").createHash("sha1").update(body).digest("hex") +
'"';
if (req.headers["if-none-match"] === etag) {
return res.status(304).end();
}
res.set("ETag", etag);
res.set(
"Cache-Control",
"public, s-maxage=120, max-age=0, stale-while-revalidate=60"
);
res.type("application/json").send(body);
});CDN-Caching: Cache-Keys und gezielte Invalidierung
Bei einem CDN entscheidet nicht nur die TTL über die Effizienz des Cachings. Mindestens genauso wichtig ist die Frage, wann zwei Requests tatsächlich dieselbe gecachte Antwort verwenden dürfen.
- Nutze Surrogate Keys bzw. Cache-Tags, um zusammengehörige Inhalte gezielt invalidieren zu können.
- Bevorzuge eine Kombination aus Soft-TTL,
stale-while-revalidateund gezielten Purges statt großflächiger Panic-Purges. - Halte Cache-Keys möglichst klein und vorhersehbar.
- Ignoriere Cookies und Header, die keinen Einfluss auf die tatsächliche Antwort haben.
Ein typischer Anwendungsfall für Cache-Tags ist eine Produktkategorie. Ändert sich die Kategorie, lassen sich alle zugehörigen Seiten gezielt invalidieren, ohne den gesamten CDN-Cache zu leeren.
Clientseitiges Caching mit SWR oder TanStack Query
Auch auf Clientseite lohnt sich eine klare Strategie für bereits geladene Daten. Wichtige Fragen sind dabei:
- Wie lange gelten Daten als aktuell?
- Wann soll automatisch neu geladen werden?
- Wie lange sollen nicht mehr verwendete Daten im Speicher bleiben?
- Soll die Benutzeroberfläche schon reagieren, bevor der Server die Änderung bestätigt?
Ein häufig verwendetes Pattern: Cached Data sofort in der UI anzeigen und im Hintergrund revalidieren. Bei Änderungen kommen zusätzlich Optimistic Updates infrage – die UI reagiert sofort und gleicht ihren Zustand anschließend mit dem Server ab.
Ein einfaches Beispiel mit SWR:
import useSWR from "swr";
const fetcher = (url) =>
fetch(url, {
headers: { accept: "application/json" },
}).then((response) => response.json());
const { data, error, isLoading } = useSWR("/api/items", fetcher, {
revalidateOnFocus: true,
dedupingInterval: 30000,
});Häufige Stolpersteine
Vary: Cookie kann die CDN-Hit-Rate massiv verschlechtern, weil schon unterschiedliche Cookies zu unterschiedlichen Cache-Einträgen führen. Wenn möglich, sollten irrelevante Auth- oder Tracking-Cookies nicht Teil des Cache-Keys sein.
Ohne ETag oder Last-Modified müssen Ressourcen häufig vollständig erneut übertragen werden, obwohl sie sich gar nicht geändert haben. Ein 304 Not Modified spart dagegen Netzwerktraffic und Verarbeitung.
immutable gehört ausschließlich zu Ressourcen, deren URL sich bei einer Änderung ebenfalls ändert. Für normales HTML ist es deshalb normalerweise ungeeignet.
no-store ist ein sinnvoller Sicherheitsmechanismus, sollte aber nicht reflexartig auf alle Antworten angewendet werden. Wer sämtliche Inhalte vom Caching ausschließt, verschenkt einen großen Teil des möglichen Performance-Gewinns.
Locale, Gerätetyp, A/B-Tests, Cookies und andere Request-Merkmale können zudem schnell zu vielen unterschiedlichen Cache-Einträgen führen. Je mehr Varianten existieren, desto geringer wird die Wahrscheinlichkeit eines Cache-Hits. Jede zusätzliche Dimension im Cache-Key sollte deshalb bewusst begründet sein.
Sinnvolle Start-Defaults
- Assets: Fingerprinting +
public, max-age=31536000, immutable - HTML:
public, s-maxage=300, max-age=0, stale-while-revalidate=60 - Öffentliche APIs:
ETag+s-maxage+ funktionierender 304-Flow - Authentifizierte Inhalte:
private, max-age=0, bei sensiblen Datenno-store - CDN: möglichst einfache Cache-Keys, Cache-Tags für gezielte Purges und möglichst wenige unnötige
Vary-Header
Diese Werte sind keine universelle Wahrheit, sondern gute Ausgangspunkte, die sich anschließend anhand des tatsächlichen Nutzungsverhaltens anpassen lassen.
Nächste Schritte
- Baseline erfassen: CDN-Hit-Rate, TTFB, übertragenes Datenvolumen und 304-Quote messen.
- Caching gezielt konfigurieren: Regeln abhängig von Route, Ressourcentyp und gewünschter Aktualität definieren.
- Caching-Budget festlegen: etwa maximale HTML-TTL, API-TTL und gewünschte Purge-Zeiten dokumentieren.
- Regeln im Repository festhalten, zum Beispiel unter
/docs/caching-guidelines.md. - Caching bei Code Reviews berücksichtigen: Neue Routes und APIs sollten bewusst entscheiden, welche Caching-Strategie sie verwenden.
Der wichtigste Maßstab bleibt Vorhersehbarkeit: eine einfache, dokumentierte Strategie mit klaren Defaults lässt sich in der Praxis meist besser nachvollziehen als ein hochkomplexes Cache-System, dessen Verhalten niemand mehr zuverlässig durchschaut.
