Caching‑Strategien, die in der Praxis wirken

6. September 2025

Caching‑Strategien, die in der Praxis wirken

Caching ist einer der effektivsten Hebel für schnelle, stabile Web‑Erlebnisse – und gleichzeitig eine häufige Fehlerquelle. Dieser Leitfaden konzentriert sich auf pragmatische Patterns, die in realen Projekten funktionieren. Ohne Over‑Engineering, mit klaren Defaults.


Ziele und Grundbegriffe

  • Freshness vs. Validation: Entweder Antwort ist »frisch« (TTL) oder wird schnell validiert (ETag/Last‑Modified → 304).
  • Public vs. Private: Öffentliche Ressourcen (CDN/Browser) vs. personenbezogene Inhalte (nur Browser, oder gezielt am CDN vorbei).
  • TTL & SWR: max-age (Zeit gültig), s-maxage (für CDNs), stale-while-revalidate (im Hintergrund erneuern), stale-if-error (bei Fehlern alt ausliefern).
  • Vary/Keying: Auf welche Request‑Attribute (z. B. Accept-Encoding, Accept-Language, Cookie) wird der Cache unterschieden.

Ebenen des Cachings

  • Browser‑Cache: Kostenlos, aber pro Nutzer. Ideal für statische Assets.
  • CDN/Edge‑Cache: Entlastet Origin, kurze Latenz global. Ideal für Assets & »public« APIs/HTML.
  • App/Server‑Cache: In‑Memory (z. B. LRU), Redis, DB‑Query‑Cache. Schnell, aber invalidierungspflichtig.
  • Client‑State‑Cache: SWR/React Query. Entkoppelt UI von Netzwerk, kontrolliert Staleness.

HTTP‑Caching: die wichtigsten Direktiven

Cache-Control: public, max-age=31536000, immutable       # Assets mit Fingerprint
Cache-Control: public, s-maxage=300, max-age=0, stale-while-revalidate=60  # HTML/API über CDN
Cache-Control: private, no-store                         # Hochsensible, personalisierte Inhalte
ETag: "abc123"                                          # Validierungstoken für 304
Vary: Accept-Encoding, Accept-Language                   # Unterschiedliche Repräsentationen

Tipps:

  • Nutze s-maxage für CDNs (überschreibt max-age für Shared Caches).
  • immutable nur bei versionierten Assets (z. B. app.7f3c1.js).
  • Bevorzuge ETag‑Validierung für häufig wechselnde, aber cachebare Ressourcen.

Pragmatiker‑Patterns (Startkonfigurationen)

  1. Statische Assets (JS/CSS/Fonts/Images, fingerprinted)
  • Cache-Control: public, max-age=31536000, immutable
  • Fingerprinting über Dateinamen (Hash) ist Pflicht.
  1. HTML‑Dokumente (öffentlich, aber dynamisch)
  • Cache-Control: public, s-maxage=300, max-age=0, stale-while-revalidate=60
  • CDN liefert sofort, revalidiert im Hintergrund; Browser hält praktisch nicht.
  1. GET‑APIs (öffentlich/teil‑öffentlich)
  • ETag setzen; Client sendet If-None-Match → schnelle 304.
  • Cache-Control: public, s-maxage=60–300, stale-while-revalidate=60 je nach Freshness‑Bedarf.
  1. Authentifizierte Seiten/API
  • Standard: Cache-Control: private, max-age=0 oder no-store bei Sensitivem.
  • Alternative (fortgeschritten): Personalisierung nur clientseitig; HTML bleibt public‑cachebar (Edge‑Key ignoriert Auth‑Cookies).
  1. Bilder
  • Responsive Sets (srcset, sizes), moderne Formate (AVIF/WebP), serverseitige Limits (Breite/Höhe) und Lazy Loading. Caching wie bei Assets.

Beispiel: Express/Node Header‑Setup

// Assets (mit Fingerprints)
app.use(
  "/assets",
  express.static("public/assets", {
    setHeaders: (res) => {
      res.set("Cache-Control", "public, max-age=31536000, immutable");
    },
  })
);
 
// HTML (CDN‑freundlich)
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‑Spezifika (Surrogate Keys & Invalidation)

  • Nutze Surrogate/Cache‑Tags, um zusammenhängende Inhalte gezielt zu invalidieren (z. B. alle Seiten einer Kategorie).
  • Strategien: Soft‑TTL (SWR) + selektive Purges statt globaler »Panic Purge«.
  • Achte auf Cache‑Keys: Ignoriere irrelevante Cookies/Headers, um Fragmentierung zu vermeiden.

Client‑seitiges Caching (SWR/React Query)

  • staleTime: Wie lange Daten als frisch gelten (UI zeigt sofort an, optional Hintergrund‑Refresh).
  • cacheTime: Wie lange im Speicher bleiben (Tab‑Wechsel etc.).
  • Pattern: Optimistic Updates + Hintergrund‑Revalidation.

Beispiel (SWR):

import useSWR from "swr";
const fetcher = (url) =>
  fetch(url, { headers: { accept: "application/json" } }).then((r) => r.json());
const { data, error, isLoading } = useSWR("/api/items", fetcher, {
  staleTime: 30000,
  revalidateOnFocus: true,
});

Häufige Stolpersteine

  • »Vary: Cookie« killt den CDN‑Hit‑Rate. Halte Auth‑Cookies aus dem Cache‑Key, wenn möglich.
  • Fehlende ETag/Validatoren → unnötige Bytes statt 304s.
  • HTML versehentlich ewig gecached (kein Fingerprint, aber immutable).
  • no-store zu großzügig → verschenkter Speed.
  • Zuviele Varianten (Locale, Device, AB‑Tests) ohne klares Keying → Cache‑Fragmentierung.

Checkliste (Start‑Defaults)

  • Assets: Fingerprinting + public, max-age=31536000, immutable.
  • HTML: public, s-maxage=300, max-age=0, stale-while-revalidate=60.
  • APIs: ETag + s-maxage + 304‑Flow.
  • Auth: private, max-age=0 oder personalisierte Teile clientseitig.
  • CDN: Sauberes Cache‑Key, Tags für Purges, keine unnötigen Vary‑Header.

Nächste Schritte

  • Miss Baseline: CDN‑Hit‑Rate, TTFB, Transfer‑Volumen, 304‑Quote.
  • Setze die oben genannten Defaults route‑/asset‑spezifisch um.
  • Führe ein »Cache‑Budget« ein (z. B. HTML TTL, API TTL, Purge‑SLA).
  • Dokumentiere Regeln im Repo (/docs/caching-guidelines.md) und prüfe sie in PRs.

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.