Startseite / Artikel / Verständnis der „use cache“-Direktive und der tagbasierten Neuvalidierung in Next.js 16

Verständnis der „use cache“-Direktive und der tagbasierten Neuvalidierung in Next.js 16

Erfahren Sie, wie die „use cache“-Direktive in Next.js 16 funktioniert, welche damit verbundenen Funktionalitäten zur Neuvalidierung vorhanden sind und wie man tenant-sensitives Caching in mehrfach genutzten Anwendungen anwenden kann.

1215 Wörter

Caching in Next.js galt traditionell als etwas undurchsichtig – eine Mischung aus Einstellungen auf Dateiebene, Optionen, die an fetch übergeben werden, sowie Framework-Standardwerten, die zwischen den Versionen so stark gewechselt haben, dass selbst erfahrene Entwickler die Dokumentation immer griffbereit hatten, nur um auf der sicheren Seite zu sein. Die in Next.js 16 eingeführte "use cache"-Direktive, die Teil des umfassenderen Cache Components-Modells ist, ändert dies, indem sie es ermöglicht, das Caching-Verhalten explizit auf Ebene einer Komponente oder Funktion zu deklarieren, anstatt es implizit von Standardwerten zu erben, die man sich merken muss.

Im Folgenden wird detailliert erläutert, was die Direktive tatsächlich bewirkt und in welchen Situationen es sinnvoll ist, sie zu verwenden.

Was die Direktive tatsächlich bewirkt

Durch das Hinzufügen von "use cache" innerhalb einer Funktion oder Komponente weist man Next.js an, alles zu cachen, was diese Funktion zurückgibt. Konzeptionell ähnelt dies der Funktion "use client", mit dem Unterschied, dass anstelle einer Abgrenzung für die Client-Seite eine Abgrenzung für das Caching festgelegt wird:

async function getDashboardStats(tenantId: string) {
  "use cache";
  const stats = await db.query.stats.findMany({ where: { tenantId } });
  return stats;
}

Die Ausgabe der Funktion wird gecacht und anhand der übergebenen Argumente gekennzeichnet, um anschließend bei nachfolgenden Anfragen wiederverwendet zu werden – bis etwas sie ungültig macht. Das unterscheidet sich deutlich vom älteren Ansatz, bei dem entweder einzelne Fetch-Aufrufe oder ganze Route-Teile gecacht wurden: Jetzt kann man auf jeder Granularitätsstufe cachen, die zu den eigenen Daten passt – bis hin zu einer einzelnen Funktion.

Die drei begleitenden Funktionen

Neben dieser Direktive führen Cache Components eine kleine Reihe von APIs ein, um gezielt Daten zu verwalten, anstatt einfach abzuwarten, bis eine Zeitüberschreitung eintritt:

  • revalidateTag(tag) – löscht alle Cache-Einträge, die mit einem bestimmten Tag verknüpft sind. Dies ist nützlich, wenn eine einzige Änderung Daten berührt, auf die mehrere verschiedene gecachten Funktionen angewiesen sind.
  • updateTag(tag) – eine eingeschränktere Version derselben Idee, die darauf abzielt, spezifische Cache-Einträge, die mit einem bestimmten Tag verbunden sind, zu aktualisieren, anstatt alles darunter.
  • refresh() – aktualisiert die für die aktuelle Anfrage gespeicherten Daten.

Dies ist das Muster, das das gesamte System zum Funktionieren bringt: Weisen Sie jeder gecachten Funktion eine aussagekräftige Kennung zu – wie zum Beispiel tenant-stats oder invoice-list – und rufen Sie immer dann revalidateTag mit der entsprechenden Kennung auf, wenn eine Änderung stattfindet, die die zugrunde liegenden Daten beeinflusst, anstatt zu versuchen, ein zeitbasiertes Ablaufdatum festzulegen, das entweder zu kurz ist (was dem Zweck des Cachens widerspricht) oder zu lang, wodurch veraltete Daten bereitgestellt werden.

async function getInvoices(tenantId: string) {
  "use cache";
  cacheTag(`invoices-${tenantId}`);
  return db.query.invoices.findMany({ where: { tenantId } });
}
// After creating an invoice:
async function createInvoice(data: InvoiceInput) {
  await db.insert(invoices).values(data);
  revalidateTag(`invoices-${data.tenantId}`);
}

Diese Kombination – Kennzeichnung beim Lesen, Neuvalidierung beim Schreiben – ist im Grunde die gesamte Idee in kompakter Form. Fast alles andere, was man mit diesem System tun wird, ist eine Variation dieser gleichen Kombination.

Häufige Verwirrungspunkte

Der häufigste Fehler, in den Teams geraten, besteht darin anzunehmen, dass "use cache" eine einfache Alternative zur Option next: { revalidate } bei fetch() ist. Tatsächlich beheben sie unterschiedliche, wenn auch miteinander verbundene Probleme. Die Option auf Fetch-Ebene steuert eine einzelne Netzwerkanfrage. "use cache" hingegen speichert den Ergebnis einer ganzen Funktion oder Komponente, die intern weitaus mehr als nur eine Aufgabe ausführen kann – beispielsweise auf eine Datenbank zugreifen, Berechnungen durchführen oder sogar selbst fetch aufrufen. Wenn man lediglich einen Aufruf einer externen API speichern möchte, ist das Caching auf Fetch-Ebene in der Regel die einfachere Wahl. "use cache" lohnt sich erst, wenn man ein gesamtes berechnetes Ergebnis speichern möchte, nicht nur eine einzelne darin enthaltene Anfrage.

Der zweite häufige Fehler besteht darin, den Tagging-Schritt völlig zu überspringen, wodurch man später verwirrt ist, wenn eine Mutation die im Cache gespeicherten Daten nicht entfernt, wie es eigentlich der Fall sein sollte. Ohne angehängten Tag gibt es nur das Zeitprinzip als Mechanismus zur Invalidation, was einen großen Teil der Gründe untergräbt, warum dieses Modell überhaupt verwendet wird – man hat die zusätzliche Komplexität des expliziten Cachings aufgenommen, ohne echte Kontrolle über die Invalidation zu erhalten.

Tenant-bewusste Tags für Multi-Tenant-Anwendungen

Falls Sie an einer Lösung für mehrere Nutzer arbeiten, gibt es einen wichtigen Punkt, den man unbedingt berücksichtigen sollte: Die Tags müssen den jeweiligen Nutzer kodieren, nicht nur die Art der gespeicherten Daten. Ein allgemeines Tag wie invoices, das von allen Nutzern gemeinsam verwendet wird, bedeutet, dass die Invalidation der Daten eines Nutzers auch für alle anderen gilt – was entweder zu Problemen in Bezug auf Korrektheit führt (ein Nutzer erhält veraltete Ergebnisse, weil eine Änderung eines anderen Nutzers eine gemeinsame Neuvollständigung auslöst) oder zu Leistungsproblemen (der Cache wird viel häufiger geleert, als nötig). Ein Format wie invoices-${tenantId}, wie im obigen Beispiel verwendet, ist keine stilistische Wahl – es ist das, was eine funktionierende Invalidation-Strategie von einer fehlerhaften unterscheidet.

Warum es sich lohnt, dies von Anfang an richtig einzurichten

Eine Caching-Schicht mit einem subtilen Fehler bricht selten auf offensichtliche Weise zusammen. Stattdessen treten meist vage Support-Anfragen wie „Warum zeigt das Dashboard immer noch die Zahlen von letzter Woche?“ auf – Fehler, deren Ursache wirklich schwierig zu ermitteln ist, da der fehlerhafte Validierungspfad oft an einem Ort liegt, an dem seit Monaten niemand mehr gearbeitet hat. Die frühzeitige Einführung einer konsistenten Tagging-Strategie, die einheitlich auf allen in der Codebasis gespeicherten Funktionen angewandt wird, gehört zu jenen Infrastrukturentscheidungen, die zu Beginn kostengünstig richtig umgesetzt werden können, aber später erheblich teurer zu beheben sind.

Einige Starter-Kits für Dashboards basieren genau auf dieser Vorgehensweise bei der Strukturierung ihrer Datenschicht. Als Beispiel wendet ein Template wie Ovyqens Dashboard-Template für Next.js- und SaaS-Projekte überall das Prinzip „Tag bei Lesevorgang, Neuvalidierung bei Schreibvorgang“ an und integriert die Identifikatoren der Nutzer von Anfang an in jedes Tag, anstatt sie nach einem Fehler im Cross-Nutzer-Caching nachträglich hinzuzufügen. Wenn Sie Dashboard-Templates vergleichen und die Cache-Invalidierung der Teil Ihres bestehenden Apps ist, dem niemand wirklich vertraut, dann ist das ein guter Grund, mit einer Basis zu starten, die dieses Problem bereits richtig handhabt.

Häufig gestellte Fragen

Muss jeder fetch()-Aufruf auf "use cache" migriert werden? Nicht unbedingt – die beiden Methoden überschneiden sich, sind aber nicht austauschbar. Caching auf Fetch-Ebene eignet sich weiterhin gut für einfache Szenarien mit einzelnen Anfragen. Wenden Sie "use cache" an, wenn Sie ein berechnetes Ergebnis oder die Ausgabe eines gesamten Komponenten speichern möchten.

Ist "use cache" ausgereift genug für Produktions-Dashboards? Gehen Sie damit genauso um wie mit jedem relativ jungen Caching-Mechanismus – testen Sie sorgfältig Ihre Validierungswege, insbesondere bei mehrfach genutzten Daten, bevor Sie es für kundenorientierte Anwendungen einsetzen, bei denen Aktualität wichtig ist.

Was passiert, wenn eine in Cache gespeicherte Funktion unmarkiert bleibt? Das Caching findet weiterhin statt, doch Sie verlieren die Möglichkeit, es gezielt aufgrund eines bestimmten Ereignisses zu ungültig zu machen. Sie sind dann ausschließlich auf die zeitbasierte Ablaufzeit angewiesen, was selten das gewünschte Verhalten ist.

Einsatz von explizitem Caching erfordert mehr Vorarbeit als das einfache Vertrauen auf die Standardeinstellungen eines Frameworks. Doch bei Dashboard-Daten, bei denen das Anzeigen veralteter Informationen echte Kosten mit sich bringt, ist diese zusätzliche Arbeit fast immer der richtige Kompromiss.

Verwandte Artikel

  • 20 fortgeschrittene Next.js-Muster für Anwendungen mit App Router auf Produktionsniveau — Lernen Sie zwanzig fortgeschrittene Next.js-Muster aus dem Bereich Server-First-Design, Streaming, Caching, Routing und Leistung, um schnellere, skalierbare Produktionsanwendungen zu entwickeln.
  • Wie sich die Frontend-Entwicklung von einfachem Styling zu skalierbaren Systemen entwickelt hat — Es wird der Wandel von grundlegendem HTML/CSS/JS zu komponentenbasierten Architekturen, Caching-Strategien, Monorepos sowie Mechanismen zur Überwachung dargestellt, die notwendig sind, um Millionen von Nutzern zuverlässig zu bedienen.