Cache-Tags und revalidateTag (Next.js)
Kurz: Next.js kann Serverdaten mit einem benannten “Tag” cachen (statt nur nach Zeit) — ein gezielter Aufruf von revalidateTag() macht dann NUR die mit diesem Tag versehenen, gecachten Daten ungültig, ohne den restlichen Cache anzufassen.
Genauer: Erlaubt eine Kombination aus TTL (Time-to-live, als Sicherheitsnetz) und gezielter, sofortiger Invalidierung bei genau den Aktionen, die die zugrunde liegenden Daten wirklich ändern. Seit Next.js 16 verlangt revalidateTag() ein zweites Argument (Cache-Profil, z. B. "max") — das steuert Stale-While-Revalidate-Verhalten: der Tag wird als veraltet markiert, aber nicht mehr zwingend sofort und hart für alle Clients gepurged wie in älteren Next.js-Versionen ohne dieses zweite Argument.
Kontext bei uns: app/utils/homeContentCache.ts bei Emzett cached Startseiten-Posts/Featured-Reviews mit unstable_cache, 60s TTL + gezielter Invalidierung per revalidateTag(HOME_CONTENT_CACHE_TAG, "max") bei jeder Post-Änderung. Bewusst NICHT für die Shop-Produktliste genutzt — der Live-Lagerbestand bei limitierten Drops muss exakt sein, ein gecachtes “noch 3 Stück” könnte zu Overselling führen.
Im Detail
Grundmuster
Eine Funktion wird mit unstable_cache() umschlossen und bekommt dabei einen oder mehrere Tags mit auf den Weg:
const getHomeContent = unstable_cache(
async () => db.query.posts.findMany({ where: eq(posts.featured, true) }),
["home-content"],
{ tags: [HOME_CONTENT_CACHE_TAG], revalidate: 60 },
);Ändert sich ein Post, ruft die zuständige Server Action revalidateTag(HOME_CONTENT_CACHE_TAG, "max") auf — der Cache-Eintrag wird sofort für ungültig erklärt, unabhängig von der noch laufenden 60-Sekunden-TTL. Das kombiniert die Vorteile beider Welten: TTL als Sicherheitsnetz (falls eine Invalidierung mal vergessen wird, ist der Cache spätestens nach der TTL wieder frisch), gezielte Invalidierung für sofortige Konsistenz bei bekannten Änderungen.
Warum nicht einfach ein globales Tag für alles?
Wichtig bei mehreren unabhängigen Cache-Bereichen: möglichst spezifische, unterschiedliche Tags verwenden (statt eines einzigen globalen Tags für alles) — sonst invalidiert eine kleine Änderung an einer Stelle unnötig auch ganz andere, unveränderte Caches mit, was den Sinn des Cachings (weniger Datenbank-Last) untergräbt. Eine gängige Konvention ist, Tags hierarchisch zu benennen (z. B. posts, posts:featured, posts:${postId}) und bei einer Änderung gezielt nur die betroffene Ebene zu invalidieren — ein einzelner geänderter Post muss nicht zwingend die komplette Featured-Liste ungültig machen, wenn er selbst gar nicht featured ist.
Unterschied zu zeitbasiertem Caching allein
Reines TTL-basiertes Caching (revalidate: 60 ohne Tags) ist einfacher, hat aber einen strukturellen Nachteil: Zwischen einer echten Datenänderung und dem nächsten TTL-Ablauf sehen Nutzer veraltete Daten — bei einer 60-Sekunden-TTL im schlimmsten Fall fast eine ganze Minute lang. Tag-basierte Invalidierung schließt dieses Fenster für BEKANNTE Änderungen praktisch auf null, während die TTL weiterhin als Sicherheitsnetz für UNBEKANNTE oder vergessene Änderungswege (z. B. ein direkter Datenbank-Eingriff ohne den regulären Code-Pfad) greift.
Das zweite Argument seit Next.js 16
Das seit Next.js 16 verpflichtende zweite Argument von revalidateTag() (ein Cache-Profil wie "max") steuert, wie aggressiv die Invalidierung ist. Frühere Next.js-Versionen kannten nur ein hartes, sofortiges Purgen für alle Clients gleichzeitig — das neue, granularere Modell erlaubt stattdessen Stale-While-Revalidate-artiges Verhalten, bei dem bereits ausgelieferte, noch im Browser/CDN vorgehaltene Antworten nicht zwingend augenblicklich verworfen werden müssen, sondern im Hintergrund aktualisiert werden können. Das ist einer der Next.js-16-Fallstricke, bei dem älteres Trainings-Wissen zu KI-Assistenten in die Irre führen kann, da revalidateTag() früher nur ein einziges Argument akzeptierte.
Siehe auch: Next.js App Router, React.cache(), TTL