Zum Inhalt springen

Next.js-Caching: die vier Ebenen - und welche Sie erwischt hat

Request-Memoisierung, Data Cache, Full Route Cache und Router Cache sind vier getrennte Mechanismen mit vier getrennten Lebensdauern. Die meisten Caching-Fehler sind eine Verwechslung zwischen ihnen.

6 Min. Lesezeit

Fast jede Caching-Beschwerde, die uns erreicht, läuft auf denselben Satz hinaus: "Wir haben die Daten geändert, und die Seite hat sich nicht aktualisiert." Das ist eine brauchbare Beschreibung des Symptoms und eine nutzlose Beschreibung der Ursache, denn Next.js hat vier Caches, und die Lösung ist in jedem eine andere.

Es lohnt sich, sie auseinanderzuhalten: Sie haben unterschiedliche Schlüssel, unterschiedliche Lebensdauern und unterschiedliche Wege, ungültig gemacht zu werden.

Die vier Ebenen

EbeneLebtGeltungsbereichGeleert durch
Request-MemoisierungEinen Render-DurchlaufServerNichts - läuft von selbst ab
Data CacheÜber Requests und Deployments hinwegServerrevalidateTag, revalidatePath, Zeit
Full Route CacheBis zur Revalidierung oder zum RedeployServerDieselben Aufrufe, dazu ein neuer Build
Router CacheSekunden bis MinutenDer Browser eines BesuchersNavigation, router.refresh()

Lesen Sie diese Tabelle zweimal. Drei davon leben auf dem Server und einer im Tab des Besuchers - und der im Tab erzeugt die Meldungen, die keinen Sinn ergeben: Die Seite ist im Inkognito-Fenster korrekt und für die Person, die das Ticket geschrieben hat, veraltet.

Request-Memoisierung

Innerhalb eines Renderings werden zwei identische fetch-Aufrufe mit derselben URL und denselben Optionen nur einmal ausgeführt. Das stammt von React, nicht von Next.js, und es existiert, damit ein Layout und eine Seite beide nach dem aktuellen Nutzer fragen können, ohne dass Sie ihn durch Props durchreichen.

// Beide laufen; nur eine Anfrage verlässt den Server.
const user = await fetch('https://api.example.com/me').then((r) => r.json());

Sie gilt für einen Render-Durchlauf. Sie kann nicht veralten, sie kann nicht geleert werden, und sie ist so gut wie nie die Ebene, die Ihren Fehler verursacht hat. Nützlich vor allem, damit Sie aufhören, Caching-Code zu schreiben, der sie dupliziert.

Der Data Cache

Das ist die Ebene, die man wirklich verstehen sollte. Sie speichert das Ergebnis von fetch auf dem Server, sie überlebt Requests, und sie überlebt Deployments.

Der letzte Punkt überrascht die meisten. Ein Redeploy leert den Data Cache nicht.

// Gecacht, bis etwas das Tag ungültig macht.
const posts = await fetch('https://cms.example.com/posts', {
  next: { tags: ['posts'] },
});
 
// Höchstens 60 Sekunden gecacht.
const rates = await fetch('https://api.example.com/rates', {
  next: { revalidate: 60 },
});
 
// Nie gecacht.
const cart = await fetch('https://api.example.com/cart', {
  cache: 'no-store',
});

Tags sind der Mechanismus, der eine Content-Website beherrschbar macht. Taggen Sie den Fetch und lassen Sie den CMS-Webhook ihn leeren:

// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
 
export async function POST(request: Request) {
  const secret = request.headers.get('x-webhook-secret');
  if (secret !== process.env.REVALIDATE_SECRET) {
    return new Response('Unauthorised', { status: 401 });
  }
 
  revalidateTag('posts');
  return Response.json({ revalidated: true });
}

Die Prüfung des Secrets ist nicht optional. Ein unauthentifizierter Revalidierungs-Endpunkt ist ein kostenloser Cache-Leeren-Knopf für jeden, der die URL findet - und den Cache einer stark besuchten Seite zu leeren heißt, dass alle Anfragen gleichzeitig zum Origin gehen.

Der Full Route Cache

Hat eine Route keine dynamischen Eingaben, rendert Next.js sie zur Build-Zeit und liefert das gespeicherte HTML samt RSC-Payload aus. Das ist der Cache, den man meint, wenn man "statisch" sagt.

Interessant ist, wie leicht eine Route ihn verlässt. cookies(), headers() oder searchParams zu lesen oder ein fetch mit cache: 'no-store' aufzurufen, zwingt die gesamte Route ins dynamische Rendering. Ein Helfer tief in einer gemeinsamen Komponente genügt, und das einzige sichtbare Signal ist die Build-Ausgabe:

Route (app)
┌ ○ /                     static
├ ƒ /products             dynamic     <- war letzte Woche statisch
└ ○ /about                static

Zeigt eine Route, die Sie für statisch hielten, ein ƒ, hat etwas darunter einen Wert zur Request-Zeit gelesen. Was genau, findet man durch Lesen des Baums, nicht durch Raten - und es lohnt sich, denn eine dynamische Route kostet Sie das CDN.

Der Router Cache

Dieser lebt im Browser des Besuchers, und er ist der Grund, warum Ihre Kollegin alte Daten sieht, nachdem Sie ihr gesagt haben, es sei behoben.

Navigiert ein Besucher clientseitig, wird der RSC-Payload des Ziels für ein kurzes Fenster im Speicher gehalten. Navigiert man innerhalb dieses Fensters weg und zurück, wird nichts neu geladen. Ein harter Reload leert ihn, eine weiche Navigation nicht.

Leeren Sie ihn nach einer Mutation explizit:

'use client';
 
import { useRouter } from 'next/navigation';
 
export function DeleteButton({ id }: { id: string }) {
  const router = useRouter();
 
  async function remove() {
    await fetch(`/api/items/${id}`, { method: 'DELETE' });
    router.refresh(); // Client-Cache verwerfen, auf dem Server neu rendern.
  }
 
  return <button onClick={remove}>Löschen</button>;
}

In einer Server Action erledigt revalidatePath beide Hälften - es leert den Server-Cache und markiert den Client-Cache als veraltet. Deshalb gehören Mutationen in Actions und nicht in handgeschriebene Route Handler, wann immer Sie die Wahl haben.

Wie wir das in der Praxis debuggen

Die Frage lautet immer "welche Ebene", und sie ist in drei Schritten beantwortbar.

  1. Ist es für alle veraltet oder für eine Person? Für eine Person: der Router Cache. Für alle: es liegt auf dem Server.
  2. Behebt ein Redeploy es? Wenn ja, war es der Full Route Cache. Wenn nein, ist es der Data Cache - der, noch einmal, Deployments überlebt.
  3. Zeigt die Route ein ƒ in der Build-Ausgabe? Wenn ja und Sie erwartet haben, debuggen Sie gar keinen Cache. Sie debuggen ein versehentliches dynamisches Rendering, ein anderes und meist teureres Problem.

Die meisten Teams, mit denen wir arbeiten, brauchen keine aggressivere Caching-Strategie. Sie müssen wissen, mit welcher der vier Ebenen sie gerade streiten - und brauchen eine Namenskonvention für Tags, an die sie sich tatsächlich halten.

Das, und das Rendering-Modell darunter - das App-Router-Rendering-Modell, richtig erklärt.

Zurück zu allen Artikeln