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 ETag oder Last-Modified. Ist die Ressource unverändert, reicht eine schlanke 304 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-age legt fest, wie lange eine Ressource als gültig gilt. s-maxage gilt speziell für Shared Caches wie CDNs. Mit stale-while-revalidate lassen sich veraltete Inhalte kurzfristig weiter ausliefern, während sie im Hintergrund aktualisiert werden. stale-if-error erlaubt 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-Language oder 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 Ressource

Ein paar praktische Regeln dazu:

  • Nutze s-maxage, sobald ein CDN oder ein anderer Shared Cache beteiligt ist. Für diese Caches überschreibt der Wert max-age.
  • Verwende immutable nur für versionierte Assets, etwa app.7f3c1.js.
  • Für Inhalte, die sich regelmäßig ändern, aber grundsätzlich cachebar sind, ist eine Validierung über ETag hä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, immutable

Voraussetzung ist konsequentes Fingerprinting über den Dateinamen: Sobald sich der Inhalt ändert, ändert sich auch die URL.

app.7f3c1.js
styles.91ac2.css

Damit 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=60

Das 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=0

Bei besonders sensiblen Daten:

Cache-Control: no-store

Eine 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 srcset und sizes
  • 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-revalidate und 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 Daten no-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

  1. Baseline erfassen: CDN-Hit-Rate, TTFB, übertragenes Datenvolumen und 304-Quote messen.
  2. Caching gezielt konfigurieren: Regeln abhängig von Route, Ressourcentyp und gewünschter Aktualität definieren.
  3. Caching-Budget festlegen: etwa maximale HTML-TTL, API-TTL und gewünschte Purge-Zeiten dokumentieren.
  4. Regeln im Repository festhalten, zum Beispiel unter /docs/caching-guidelines.md.
  5. 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.

Weiterführende Artikel

  • Performance Budgets, die wirklich wirken

    Wie Teams messbare Performance‑Ziele (LCP, INP, Bytes) festlegen, im Prozess verankern und automatisiert prüfen – pragmatisch für Entwickler, Product Owner und andere Nerds.