Journal / Entwicklung von Webprodukten

Wie der Cache in Next.js v13+ mit dem App Router funktioniert

Wie der Cache in Next.js v13+ mit dem App Router funktioniert

Ein ausführlicher Überblick über die Funktionsweise des Caches in Next.js 13+, welche Cache-Arten es gibt und wie sie sich auf Performance und SEO auswirken.

Was ist ein Cache und wie funktioniert er in Next.js

Ein Cache speichert die Ergebnisse von Berechnungen oder Anfragen, sodass sie nicht wiederholt werden müssen – das beschleunigt die Anwendung. In Next.js können HTML-Seiten, Daten, Anfragen, das Rendering von Komponenten und sogar ganze Routen im Cache abgelegt werden.

Mit dem App Router (ab Next.js 13) hat sich die Philosophie geändert: Der Cache ist jetzt standardmäßig aktiv, und es liegt beim Entwickler, ihn abzuschalten, wenn dynamisches Verhalten gebraucht wird.

Die Richtlinie von Next.js lautet:

„Wir cachen alles, sofern nichts anderes angegeben ist"

Wenn Sie nicht ausdrücklich cache: 'no-store', next: { revalidate } oder dynamic = 'force-dynamic' angeben, dann gilt:

  • fetch() wird im Cache abgelegt
  • Layout und Seite können ebenfalls gecacht werden
  • Routen werden dem Routen-Cache hinzugefügt
  • das Rendering der Seite geht in den vollständigen Routen-Cache

Das sorgt für Performance, kann aber zu veralteten Daten und Problemen beim Debugging führen.

Cache-Arten in Next.js

Quelle: Next.js-Dokumentation

Full Route Cache (serverseitiger Routen-Cache)

Wird mit ISR (Incremental Static Regeneration) verwendet.

  • Die gesamte Seite wird gecacht (HTML, Daten aus layout.tsx, page.tsx, fetch(), headers(), cookies()).
  • Der Cache liegt auf dem Server.
  • Funktioniert standardmäßig, wenn es kein dynamisches Verhalten gibt.
  • Wird über revalidate oder revalidateTag() deaktiviert.

Am besten für Blogs, Marketingseiten und andere selten wechselnde Inhalte.

Beispiel:

export const revalidate = 60; // regenerate the page every 60 seconds

Data Cache (serverseitiger Cache für Datenabrufe)

  • Antworten von fetch() in Server Components werden gecacht.
  • Gesteuert über next: { revalidate } oder cache: 'no-store'.

Beispiel:

await fetch('https://api.com/posts', {
  next: { revalidate: 300 },
});

Request Memoization (Cache pro Anfrage, serverseitig)

Das ist eine interne Optimierung von Next.js: Dasselbe fetch() oder dieselbe Funktion mit denselben Argumenten wird pro Rendering nur einmal ausgeführt.

  • Innerhalb eines Server-Renderings verfolgt Next.js identische Anfragen.
  • Wurden sie bereits ausgeführt, kommt das Ergebnis aus dem Cache.
  • Funktioniert unabhängig von cache: 'force-cache' oder no-store.

Gut für:

  • DRY (don't repeat yourself) beim Abrufen derselben Daten in verschiedenen Komponenten.
  • Bessere Performance ohne manuelle Optimierung.
  • Wenn dasselbe fetch sowohl in layout.tsx als auch in page.tsx verwendet wird.

Nicht für:

  • Wiederverwendung über verschiedene Anfragen oder Nutzer hinweg (es ist kein globaler Cache).
  • Das Umgehen des Data Cache – er arbeitet auf Basis der Request Memoization.
// lib/getUser.ts
export async function getUser() {
  return fetch('https://api.com/user').then(res => res.json());
}

// app/layout.tsx
export default async function Layout({ children }) {
  const user = await getUser(); // Call 1
  return (
    <div>
      <Sidebar user={user} />
      {children}
    </div>
  );
}

// app/page.tsx
import { getUser } from '../lib/getUser';

export default async function Page() {
  const user = await getUser(); // Call 2, but actually not repeated
  return <p>Hello, {user.name}</p>;
}

Physisch wird fetch nur einmal aufgerufen, und das Ergebnis wird an beiden Stellen verwendet.

Router Cache (clientseitiger Cache)

Das ist der Cache, den Sie selbst mit Bibliotheken wie SWR, React Query und anderen steuern.

  • Funktioniert nur bei clientseitiger Navigation (<Link>, router.push()).
  • Next.js kontrolliert diesen Cache nicht – Sie entscheiden, wie er sich etwas merkt.
  • Sehr schnell, weil nichts neu gerendert wird – React hängt einfach aus dem Speicher ein.
  • Aber er existiert nur, bis der Nutzer die Seite neu lädt (F5).

Am besten für interaktive Komponenten, Formulare, Dashboards.

Beispiel:

'use client';
import useSWR from 'swr';

const { data } = useSWR('/api/data', fetcher);

Visueller Vergleich der Cache-Arten

Cache-ArtWo er wirktSteuerungBeispiel
Full Route CacheServer (ganze Route)revalidate, tagexport const revalidate = 60
Data CacheServer (Datenabruf)cache, revalidatefetch(..., { next: ... })
Statisch generierter CacheServer (Build-Zeit)KeineJSX ohne Daten
Clientseitiger CacheBrowser / ClientSie selbstSWR / React Query

Ein Beispiel aus dem Alltag

Angenommen, Sie bauen einen Shop:

SeiteWas zwischengespeichert wirdWelche Cache-Art verwenden
/produktyProduktliste, Aktualisierung alle 5 MinutenFull Route Cache (ISR)
/api/produktyProdukt-API-DatenData Cache (mit revalidateTag)
SPA-NavigationSchnelle NavigationRouter Cache (clientseitiger Cache)

Typische Cache-Probleme im App Router

Daten aktualisiert – aber die Seite zeigt alte Daten

fetch() verwendet standardmäßig force-cache, wenn Sie nicht cache: 'no-store' oder next: { revalidate } angeben. Folglich merkt sich Next.js die Daten und aktualisiert sie nie.

Lösung:

  • Fügen Sie cache: 'no-store' hinzu, um das Caching auszuschalten
  • Oder next: { revalidate: 60 } für ISR
fetch('https://api.com/data', { next: { revalidate: 60 } });

Die URL (searchParams) hat sich geändert, aber die Daten sind dieselben

Next.js führt die Abfrage nicht erneut aus, wenn die URL gleich ist. Verwenden Sie Parameter, fügen Sie sie direkt in die Abruf-URL ein:

fetch(`https://api.com/posts?category=${searchParams.category}`, {
  next: { revalidate: 60 }
});

Navigation über <Link>, aber die Daten haben sich nicht aktualisiert

Es wurde der Router Cache (clientseitiger Cache) verwendet. Next.js sendet beim Zurückkehren zur Seite keine neue Anfrage.

Lösung:

  • Erzwingen Sie die Aktualisierung der Daten mit router.refresh()
  • Verwenden Sie cache: 'no-store', wenn die Daten immer aktuell sein müssen

revalidatePath() oder revalidateTag() funktioniert nicht

  • Der Cache wurde nicht angelegt (z. B. wegen no-store)
  • Sie verwenden keine tags beim Abruf
  • Sie rufen revalidatePath() außerhalb einer Server Action auf

Lösung:

  • Stellen Sie sicher, dass der Abruf next: { tags: [...] } verwendet
  • Rufen Sie revalidateTag('tag') in einer Server-Funktion auf

Das Layout ist gecacht und aktualisiert sich nicht

layout.tsx wird ebenfalls als RSC mit revalidate gecacht, und wenn die Daten darin gecacht sind, werden sie erst mit der nächsten ISR aktualisiert.

Lösung:

  • Verwenden Sie cache: 'no-store' für Abrufe in layout.tsx, wenn dort beispielsweise das Nutzerprofil steckt
  • Oder verlagern Sie den Abruf aus dem Layout in die Seite (oder in eine Client-Komponente mit SWR)

Unerwartetes Verhalten im Entwicklungsmodus (dev)

In next dev sind viele Caches abgeschaltet oder arbeiten anders, und revalidate funktioniert im Entwicklungsmodus nicht zuverlässig.

Tipp:

Testen Sie den Cache mit next build && next start oder in der Vorschau von Vercel.

Die Kombination aus cookies(), headers(), params und fetch() zerstört den Cache.

Wenn Sie dynamische Dinge verwenden wie:

cookies()

headers()

useSearchParams()

generateMetadata() mit dynamischen Werten

schaltet Next.js den Seiten-Cache automatisch ab und wechselt zu dynamic = 'force-dynamic'.

Checkliste: so vermeiden Sie Cache-Probleme

Legen Sie das Abrufverhalten immer ausdrücklich fest:

  • Vergessen Sie nicht, Query-Parameter direkt in die Abruf-URL aufzunehmen
  // no cache at all
  fetch(..., { cache: 'no-store' })

  // ISR (revalidate every 60 seconds)
  fetch(..., { next: { revalidate: 60 } })

  // manual invalidation via tag
  fetch(..., { next: { tags: ['products'] } })
  • Vergessen Sie nicht, Query-Parameter direkt in die URL zu legen, die an fetch übergeben wird
  • Testen Sie mit next build && next start – sonst wiegen Sie sich womöglich in falscher Sicherheit
  • Müssen Sie Daten manuell invalidieren, verwenden Sie revalidatePath() oder revalidateTag()

Läuft der Cache in Ihrem App-Router-Projekt bereits in Produktion und macht Probleme, genau solche Aufgaben übernehme ich als freiberuflicher Next.js-Entwickler: Cache-Audits, Korrekturen und Migrationen auf den App Router.