Next.js Caching vollständig verstehen: Wann revalidate richtig einsetzen

Die alten Daten auf dem Bildschirm – auch nach dem zwanzigsten Refresh. Vor zehn Minuten haben Sie den Titel manuell in der Datenbank geändert, aber die Seite zeigt noch immer das Alte. Im Code steht klar revalidate: 60. Frustriert löschen Sie es, setzen revalidate: 10, starten den Server neu, refreshen – immer noch alt.
Das Caching in Next.js gehört wohl zu den frustrierendsten Teilen des Frameworks. Vier Cache-Ebenen, drei revalidate-Methoden und ein Breaking Change von Version 14 auf 15. Sie dachten, revalidate: 60 reicht? Vielleicht stört der Router Cache, vielleicht wurde der Full Route Cache nicht invalidiert – oder Sie testen einfach in der Dev-Umgebung.
Dieser Artikel erklärt auf verständliche Weise:
- Wie viele Cache-Ebenen Next.js hat und wofür jede zuständig ist
- Was revalidatePath, revalidateTag und updateTag unterscheidet und wann Sie welche Methode nutzen
- Wie Sie bei nicht aktualisierten Daten Schicht für Schicht vorgehen
Wenn Sie schon einmal vor dem Problem standen, dass Daten nicht aktualisiert werden, revalidate nicht greift oder Sie nicht wissen, welche API Sie brauchen – die nächsten zwölf Minuten können Ihnen mehrere Nachtschichten ersparen.
Warum Next.js Caching so komplex ist
Warum so viele Ebenen?
Ehrlich gesagt: Als ich zum ersten Mal hörte, dass Next.js vier Cache-Ebenen hat, sah mein Gesicht vermutlich genauso aus wie Ihres jetzt. Request Memoization? Full Route Cache? Was soll das? Geht das nicht einfacher?
Bei näherer Betrachtung löst jede Ebene aber ein anderes Performance-Problem:
- Zehn Komponenten in Ihrem Baum brauchen Benutzerdaten – zehn Requests wären absurd.
- Ihre Blog-Artikelliste ändert sich selten – bei jedem Besuch neu rendern wäre Verschwendung.
- Der Nutzer klickt auf Zurück – er sollte nicht auf ein erneutes Laden warten.
Jede Cache-Ebene hat ihre Aufgabe. Das Problem: Sie beeinflussen sich gegenseitig. Deshalb wissen Sie nach einer Datenänderung oft nicht, welchen Cache Sie leeren müssen.
Next.js 14 vs. 15: Eine Cache-Revolution
Ende 2024 kam in Next.js 15 die große Nachricht: fetch-Anfragen werden standardmäßig nicht mehr gecacht.
Früher (14):
fetch(url) // Standard: Cache, entspricht cache: 'force-cache'
Jetzt (15):
fetch(url) // Standard: kein Cache, entspricht cache: 'no-store'
Viele Projekte nach dem Upgrade: Performance-Einbruch, weil zuvor automatisch gecachte Daten plötzlich bei jedem Request neu geladen wurden. Im Vercel-Forum gab es Kritik – die offizielle Begründung: „Explizit vor implizit – Caching soll eine bewusste Entscheidung des Entwicklers sein, kein Standardverhalten.“
Klingt überzeugend. Für bestehende Projekte ist es dennoch ein Breaking Change.
Überblick über die vier Cache-Ebenen
Vereinfacht gesagt durchlaufen Daten auf dem Weg vom Server zum Browser diese vier Ebenen:
-
Request Memoization (Request-Memoization)
Gültigkeitsbereich: ein einzelner Render-Zyklus
Verantwortlich: React -
Data Cache (Daten-Cache)
Gültigkeitsbereich: serverseitig, persistent über Requests
Verantwortlich: Next.js -
Full Route Cache (vollständiger Routen-Cache)
Gültigkeitsbereich: serverseitig, statische Routen
Verantwortlich: Next.js -
Router Cache (Router-Cache)
Gültigkeitsbereich: Client-Browserspeicher
Verantwortlich: Next.js
Der Datenfluss sieht ungefähr so aus:
Nutzerbesuch → Router Cache (Client) → Full Route Cache (Server)
↓
Data Cache → Request Memoization → Datenquelle
Ihr revalidate betrifft vor allem Data Cache und Full Route Cache. Den Router Cache leeren Sie mit router.refresh() oder einem Hard Refresh.
Deshalb sehen Sie manchmal nach revalidate noch alte Daten – der Router Cache ist noch aktiv.
Im nächsten Abschnitt gehen wir jede Cache-Ebene im Detail durch: was sie tut, wann sie greift und wann sie abläuft.
Die vier Cache-Ebenen im Detail
Request Memoization (Request-Memoization)
Was ist das?
Das ist eigentlich ein Feature aus React 18, nicht speziell von Next.js. Vereinfacht: Während eines Render-Zyklus führt React identische GET-Requests aus mehreren Komponenten zu einem einzigen Request zusammen.
Beispiel:
// app/page.tsx
async function UserProfile() {
const user = await fetch('https://api.example.com/user/123')
return <div>{user.name}</div>
}
async function UserAvatar() {
const user = await fetch('https://api.example.com/user/123') // gleicher Request
return <img src={user.avatar} />
}
export default function Page() {
return (
<>
<UserProfile />
<UserAvatar />
</>
)
}
Beide Komponenten rufen dieselbe URL auf – es wird aber nur ein Request gesendet. React merkt sich das Ergebnis des ersten Aufrufs und nutzt es beim zweiten.
Sehr kurze Lebensdauer
Dieser Cache gilt nur während eines Render-Durchlaufs. Nach dem Rendern ist er weg. Beim nächsten Seitenaufruf starten Sie von vorn.
Hinweise
- Gilt nur für Server Components
- Nur für GET-Requests; POST, PUT usw. werden nicht memoized
- In Dev sehen Sie den Effekt oft nicht, weil jede Codeänderung ein Re-Render auslöst
Wann ist das relevant?
Meist müssen Sie sich nicht darum kümmern. React optimiert automatisch – Sie können das nicht manuell steuern. Ich erwähne es nur, damit Sie wissen: Identische fetch-Aufrufe in mehreren Komponenten lösen nicht mehrfache Requests aus.
Data Cache (Daten-Cache)
Das ist der Kern
Der Data Cache ist das Herzstück des Next.js-Cachings. Er speichert fetch-Ergebnisse im Dateisystem des Servers – persistent über Requests, Nutzer und Deployments hinweg.
Next.js 14 vs. 15 – ein großer Unterschied
Next.js 14:
fetch('https://api.example.com/posts')
// entspricht
fetch('https://api.example.com/posts', { cache: 'force-cache' })
// Ergebnis: Daten werden dauerhaft gecacht, bis manuell revalidiert
Next.js 15:
fetch('https://api.example.com/posts')
// entspricht
fetch('https://api.example.com/posts', { cache: 'no-store' })
// Ergebnis: bei jedem Request neu abrufen, kein Cache
Daten cachen (Next.js 15)
Variante 1: Pro Request
fetch('https://api.example.com/posts', {
cache: 'force-cache',
next: { revalidate: 3600 } // nach 1 Stunde neu validieren
})
Variante 2: Pro Route
// app/blog/page.tsx
export const revalidate = 3600
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts')
// ...
}
Wann läuft der Cache ab?
Der Data Cache wird invalidiert, wenn:
- Die konfigurierte
revalidate-Zeit abläuft (z. B. 3600 Sekunden) - Sie manuell
revalidatePath()oderrevalidateTag()aufrufen - Sie die Anwendung neu deployen
Was, wenn mehrere fetch-Aufrufe unterschiedliche revalidate-Zeiten haben?
Hat eine Seite mehrere fetch-Aufrufe mit verschiedenen revalidate-Werten, nimmt Next.js den kürzesten als Revalidierungszeit für die gesamte Seite.
async function Page() {
const posts = await fetch('...', { next: { revalidate: 60 } }) // 60 Sekunden
const user = await fetch('...', { next: { revalidate: 3600 } }) // 1 Stunde
// Die gesamte Seite wird alle 60 Sekunden revalidiert
}
Full Route Cache (vollständiger Routen-Cache)
Caching auf HTML-Ebene
Während der Data Cache Daten speichert, cached der Full Route Cache das gesamte HTML und den RSC Payload (serialisierte React Server Component-Daten).
Wann wird gecacht?
Nur statisch gerenderte Routen werden gecacht. Statisch heißt: Der Inhalt ist beim Build feststellbar.
Diese Elemente erzwingen dagegen dynamisches Rendering – dann kein Cache:
cookies()headers()searchParams- Instabile Funktionen wie
Math.random(),Date.now()
Statisch oder dynamisch erkennen
npm run build
Die Ausgabe zeigt:
Route (app) Size First Load JS
┌ ○ / 5 kB 87 kB
├ ● /blog 1 kB 88 kB
└ ƒ /api/user 0 kB 87 kB
○ (Static) automatisch als statisches HTML gerendert
● (SSG) statisches HTML + JSON generiert
ƒ (Dynamic) serverseitig bei Bedarf gerendert
○ oder ● = statisch, wird gecacht. ƒ = dynamisch, kein Cache.
Statisch oder dynamisch erzwingen
// Statisch erzwingen
export const dynamic = 'force-static'
// Dynamisch erzwingen
export const dynamic = 'force-dynamic'
Wann läuft der Cache ab?
Der Full Route Cache wird invalidiert, wenn:
- Der Data Cache abläuft (Daten geändert → Seite muss neu gerendert werden)
- Sie
revalidatePath('/blog')aufrufen - Sie neu deployen
Router Cache (Router-Cache)
Clientseitiger Cache
Der Router Cache liegt im Speicher des Browsers. Nach einem Seitenbesuch speichert Next.js den Inhalt clientseitig – bei Zurück-Navigation oder erneutem Aufruf kommt er aus dem Cache, ohne neuen Server-Request.
Prefetch
Mit <Link href="/about"> prefetcht Next.js /about, sobald der Link sichtbar wird, und legt ihn im Router Cache ab. Beim Klick erfolgt der Wechsel nahezu sofort.
Lebensdauer (Next.js 14)
- Statische Routen: 5 Minuten Cache
- Dynamische Routen: 30 Sekunden Cache
Änderung in Next.js 15
Next.js 15 aktiviert den Router Cache standardmäßig nicht (bzw. mit sehr kurzer Dauer). Aktivierung in next.config.js:
// next.config.js
module.exports = {
experimental: {
staleTimes: {
dynamic: 30, // dynamische Routen 30 Sekunden
static: 180, // statische Routen 180 Sekunden
},
},
}
Wann läuft der Cache ab?
- Cache-Zeit abgelaufen
- Hard Refresh (Strg+Umschalt+R)
- Aufruf von
router.refresh()
Warum scheint revalidate nicht zu wirken?
Oft rufen Sie serverseitig revalidatePath auf, die Daten sind aktualisiert – der Nutzer sieht beim Refresh trotzdem Altes. Meist liegt es am Router Cache.
Lösungen:
- Hard Refresh (können Sie Nutzern nicht zumuten)
- Nach Datenupdate
router.refresh()in Client Components - Router-Cache-Zeit verkürzen
revalidate-Methoden im Überblick
Nach den vier Cache-Ebenen folgt der praktische Teil: Wie invalidieren Sie Cache gezielt?
Next.js bietet mehrere revalidate-Methoden für unterschiedliche Szenarien. Deren Unterschiede zu kennen spart viel Debug-Zeit.
Zeitbasiertes revalidate (Time-based)
Die häufigste Variante
Zeitbasiertes revalidate bedeutet: „Alle X Sekunden Daten neu holen.“ Das ist der Kern von ISR (Incremental Static Regeneration).
Zwei Schreibweisen
Variante 1: Im fetch
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // 3600 Sekunden = 1 Stunde
})
Variante 2: Auf Route-Ebene
// app/blog/page.tsx
export const revalidate = 3600
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts')
return <PostList posts={posts} />
}
Funktionsweise (ISR)
Bei revalidate: 3600:
- Erster Besucher → statisches HTML wird erzeugt und 1 Stunde gecacht
- In dieser Stunde sehen alle Besucher dasselbe HTML (sehr schnell)
- Nach 1 Stunde: nächster Besucher → bekommt noch das alte HTML (kein Warten)
- Gleichzeitig generiert Next.js im Hintergrund neues HTML
- Nach Fertigstellung sehen folgende Besucher den neuen Inhalt
Das ist stale-while-revalidate: Nutzer warten nie, aber jemand sieht kurz veraltete Daten.
Typische Einsatzfälle
- Blog-Artikelliste (wenige Updates pro Stunde)
- Nachrichten-Startseite (alle 30 Minuten)
- Produktkatalog (täglich)
Häufiges Problem 1: In Dev getestet
Viele beschweren sich, revalidate greife nicht. Erste Frage: Dev oder Produktion?
In Dev (npm run dev) deaktiviert Next.js den Großteil des Cachings. Testen Sie so:
npm run build
npm start
Häufiges Problem 2: Unterschiedliche Zeiten bei mehreren fetch-Aufrufen
Bei mehreren fetch-Aufrufen mit verschiedenen revalidate-Werten gilt der kürzeste für die Seiten-Revalidierung. Detail: Jeder fetch respektiert weiterhin seine eigene Data-Cache-Zeit.
async function Page() {
// Dieser Request alle 60 Sekunden
const posts = await fetch('...', { next: { revalidate: 60 } })
// Dieser Request alle 3600 Sekunden
const user = await fetch('...', { next: { revalidate: 3600 } })
}
Die Seite rendert alle 60 Sekunden neu, aber user-Daten bleiben im Data Cache 1 Stunde. In der ersten Stunde ändert sich user nicht, obwohl die Seite alle 60 Sekunden neu gerendert wird.
On-Demand revalidate: revalidatePath
Nutzer ausgelöste Updates
Zeitbasiertes revalidate ist ein Timer – revalidatePath ist der Knopf: Bei bestimmten Ereignissen (z. B. neuer Artikel) invalidieren Sie Cache manuell.
Verwendung
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function publishPost(formData) {
// Artikel veröffentlichen
await db.posts.create({ ... })
// Cache der Blog-Liste invalidieren
revalidatePath('/blog')
}
Aufruf aus Client Component:
// app/components/PublishButton.tsx
'use client'
import { publishPost } from '@/app/actions'
export function PublishButton() {
return (
<form action={publishPost}>
<button type="submit">Artikel veröffentlichen</button>
</form>
)
}
Pfadtypen
// Nur /blog
revalidatePath('/blog', 'page')
// Alle Seiten unter /blog (inkl. /blog/post-1, /blog/post-2)
revalidatePath('/blog', 'layout')
Wichtig: Keine sofortige Neugenerierung
Viele erwarten, dass die Seite nach revalidatePath sofort neu generiert wird. Das passiert nicht.
revalidatePath markiert Cache nur als ungültig. Die Neugenerierung erfolgt beim nächsten Besuch der Seite.
Ablauf:
revalidatePath('/blog')aufrufen- Cache wird geleert
- Nächster Besucher von
/blog→ HTML wird neu generiert (er wartet) - Folgende Besucher sehen den neuen Inhalt
Typische Einsatzfälle
- Liste nach neuem Inhalt aktualisieren
- Konfigurationsänderung durch Admin
- Formular absenden und aktuelle Seite refreshen
On-Demand revalidate: revalidateTag
Flexiblere Invalidierung
revalidatePath invalidiert nach Pfad – revalidateTag nach Tag. Sie versehen Daten mit Tags und invalidieren alle zugehörigen Cache-Einträge auf einmal.
Verwendung
Schritt 1: Tags beim fetch setzen
const posts = await fetch('https://api.example.com/posts', {
next: {
revalidate: 3600,
tags: ['posts'] // Tag 'posts'
}
})
const authors = await fetch('https://api.example.com/authors', {
next: {
revalidate: 3600,
tags: ['posts', 'authors'] // mehrere Tags möglich
}
})
Schritt 2: Tag invalidieren
'use server'
import { revalidateTag } from 'next/cache'
export async function publishPost() {
await db.posts.create({ ... })
// Alle Einträge mit Tag 'posts' werden invalidiert
revalidateTag('posts')
}
profile=“max” und stale-while-revalidate
Next.js 15 empfiehlt profile="max":
revalidateTag('posts', { profile: 'max' })
Mit profile="max":
- Als abgelaufen markieren, Cache nicht sofort löschen
- Nächster Besuch → alte Daten (schnell!)
- Im Hintergrund neue Daten holen
- Danach neue Daten für folgende Requests
Besser als Standardverhalten – Nutzer warten nicht.
revalidatePath vs. revalidateTag
| Kriterium | revalidatePath | revalidateTag |
|---|---|---|
| Granularität | Nach Pfad | Nach Daten-Tag |
| Seitenübergreifend | Nur konkreter Pfad | Mehrere Seiten möglich |
| Feinheit | Grob | Fein |
| Komplexität | Einfach | Tags vorab planen |
Wann Tags?
Wenn dieselben Daten auf mehreren Seiten genutzt werden.
Beispiel Blog:
- Artikelliste
/blog - Detail
/blog/[slug] - Autorenseite
/author/[id] - Modul „Neueste Artikel“ auf der Startseite
Mit revalidatePath:
revalidatePath('/blog')
revalidatePath('/blog/[slug]')
revalidatePath('/author/[id]')
revalidatePath('/')
Mit Tag posts reicht:
revalidateTag('posts')
Alle betroffenen Seiten werden invalidiert – deutlich weniger Aufwand.
Neu: updateTag (Next.js 15)
Sofort invalidieren statt verzögert
updateTag ist neu in Next.js 15. Unterschied zu revalidateTag: Cache wird sofort gelöscht, nicht nur als abgelaufen markiert.
'use server'
import { updateTag } from 'next/cache'
export async function updateUserProfile(userId, newData) {
await db.users.update({ where: { id: userId }, data: newData })
// Benutzerdaten-Cache sofort invalidieren
updateTag(`user-${userId}`)
}
Unterschied zu revalidateTag
| Kriterium | revalidateTag | updateTag |
|---|---|---|
| Invalidierung | Als abgelaufen markieren, Update im Hintergrund | Cache sofort löschen |
| Nächster Besuch | Alte Daten, Update im Hintergrund | Blockierend, neue Daten |
| Einschränkung | Überall nutzbar | Nur in Server Actions |
| Szenario | Allgemein, Geschwindigkeit priorisieren | „Read your own writes“ |
Was bedeutet „Read your own writes“?
Ein Nutzer ändert seinen Nickname im Profil – nach Speichern soll sofort der neue Name erscheinen, nicht erst nach Hintergrund-Update.
Dafür updateTag:
export async function updateProfile(formData) {
const userId = getCurrentUserId()
await db.users.update({
where: { id: userId },
data: { nickname: formData.get('nickname') }
})
// Sofort invalidieren für konsistente Anzeige
updateTag(`user-${userId}`)
revalidatePath('/profile')
}
Neu: use cache-Direktive (Next.js 15)
Caching explizit deklarieren
Next.js 15 führt 'use cache' ein – Funktionen können explizit als cachebar markiert werden.
Verwendung
'use cache'
export async function getPopularPosts() {
const posts = await db.posts.findMany({
orderBy: { views: 'desc' },
take: 10
})
return posts
}
Mit cacheTag kombinieren
import { unstable_cacheTag as cacheTag } from 'next/cache'
'use cache'
export async function getPostsByAuthor(authorId) {
cacheTag('posts', `author-${authorId}`)
return await db.posts.findMany({
where: { authorId }
})
}
Invalidierung per revalidateTag:
revalidateTag(`author-${authorId}`)
Warum nötig?
In Next.js 15 cached fetch standardmäßig nicht. Ohne explizite Angabe wird die Datenbank bei jedem Request erneut abgefragt. 'use cache' macht Ihre Absicht klar.
Fehlersuche bei Cache-Problemen
Theorie erledigt – jetzt das Praktische: Was tun, wenn der Cache Probleme macht?
Die vier häufigsten Probleme mit Prüfschritten und Lösungen.
Problem 1: revalidate greift nicht
Symptom
Sie setzen export const revalidate = 60, aber nach zehn Minuten sind die Daten noch alt.
Prüfschritte
1. In Produktion testen
Der häufigste Fehler. In Dev ist der Großteil des Cachings deaktiviert.
So testen:
npm run build
npm start
2. Dynamisches Rendering prüfen
npm run build
Ausgabe:
Route (app) Size
├ ○ /blog 1 kB ← statisch, wird gecacht
└ ƒ /profile 2 kB ← dynamisch, kein Cache
Ist die Route ƒ (dynamisch), wirkt revalidate nicht – dynamische Routen werden nicht gecacht.
Code, der dynamisches Rendering erzwingt:
// Diese APIs machen die Route dynamisch
import { cookies } from 'next/headers'
import { headers } from 'next/headers'
export default function Page({ searchParams }) { // searchParams genutzt
const cookieStore = cookies() // cookies genutzt
// ...
}
Lösung:
- Dynamisches Rendering entfernen, wenn nicht nötig
- Oder On-Demand revalidate statt zeitbasiertem revalidate nutzen
3. Next.js-Version prüfen
14 und 15 unterscheiden sich im Standardverhalten. Nach Upgrade auf 15 cached vieles nicht mehr automatisch.
Lösung (Next.js 15):
// Cache explizit aktivieren
fetch(url, {
cache: 'force-cache',
next: { revalidate: 60 }
})
// oder use cache
'use cache'
export async function getData() {
// ...
}
4. Router Cache prüfen
Auch bei aktualisierten Server-Daten kann der Router Cache alte Inhalte zeigen.
Lösung:
- Hard Refresh (Strg+Umschalt+R)
- Oder kürzere
staleTimesin Next.js 15
Problem 2: Daten aktualisiert, Seite noch alt
Symptom
Datenbank manuell geändert oder revalidatePath aufgerufen – Seite zeigt noch Altes.
Vorgehen: Vier Ebenen der Reihe nach
Ebene 1: Router Cache (Client)
Am leichtesten zu übersehen.
Schnelltest:
- Strg+Umschalt+R (Hard Refresh)
- Wenn Daten dann stimmen → Router Cache
Lösung:
'use client'
import { useRouter } from 'next/navigation'
export function RefreshButton() {
const router = useRouter()
return (
<button onClick={() => router.refresh()}>
Aktualisieren
</button>
)
}
Oder kürzere Cache-Zeit in next.config.js:
module.exports = {
experimental: {
staleTimes: {
dynamic: 0, // dynamische Routen ohne Cache
static: 30, // statische Routen 30 Sekunden
},
},
}
Ebene 2: Full Route Cache (Server)
Prüfen, ob die Route statisch ist – dann ist das gesamte HTML gecacht.
Schnelltest:
# Neues Inkognito-Fenster – wenn noch alt, serverseitiger Cache
Lösung:
'use server'
import { revalidatePath } from 'next/cache'
export async function updateData() {
await db.update({ ... })
revalidatePath('/your-page')
}
Ebene 3: Data Cache (Server)
fetch-Konfiguration prüfen.
Schnelltest:
Zeitstempel im fetch loggen:
const data = await fetch(url)
console.log('Fetched at:', new Date().toISOString())
Bleibt der Zeitstempel gleich → Cache aktiv.
Lösung:
Variante 1: Tags setzen und invalidieren
const data = await fetch(url, {
next: { tags: ['my-data'] }
})
revalidateTag('my-data')
Variante 2: Cache zum Testen deaktivieren
const data = await fetch(url, {
cache: 'no-store'
})
Ebene 4: Request Memoization (Server)
Selten problematisch – gilt nur für einen Request. Wenn die ersten drei Ebenen in Ordnung sind, liegt es vermutlich an der Datenquelle selbst.
Problem 3: revalidatePath oder revalidateTag?
Entscheidungsbaum
Cache invalidieren
|
├─ Nur eine Seite betroffen
| → revalidatePath('/specific-page')
|
├─ Alle Seiten unter einem Pfad
| → revalidatePath('/blog', 'layout')
|
├─ Daten auf mehreren verschiedenen Pfaden
| → revalidateTag('your-tag')
|
└─ Sofortige Invalidierung (Nutzer sieht Änderung sofort)
→ updateTag('your-tag') (Next.js 15)
Praxisbeispiel: Blog
Seiten:
- Startseite: 3 neueste Artikel
- Liste
/blog: alle Artikel - Detail
/blog/[slug]: ein Artikel - Autor
/author/[id]: Artikel eines Autors
Tag-Strategie:
async function getPosts() {
return fetch('https://api.example.com/posts', {
next: {
revalidate: 3600,
tags: ['posts']
}
})
}
async function getPostBySlug(slug) {
return fetch(`https://api.example.com/posts/${slug}`, {
next: {
revalidate: 3600,
tags: ['posts', `post-${slug}`]
}
})
}
async function getPostsByAuthor(authorId) {
return fetch(`https://api.example.com/posts?author=${authorId}`, {
next: {
revalidate: 3600,
tags: ['posts', `author-${authorId}-posts`]
}
})
}
Neuen Artikel veröffentlichen:
export async function publishPost(formData) {
await db.posts.create({ ... })
revalidateTag('posts')
}
Bestimmten Artikel bearbeiten:
export async function updatePost(slug, newData) {
await db.posts.update({ where: { slug }, data: newData })
revalidateTag(`post-${slug}`)
// Optional: Liste mit aktualisieren
revalidateTag('posts')
}
Problem 4: Nach Upgrade von 14 auf 15 funktioniert Caching nicht
Symptom
Nach Upgrade auf Next.js 15 werden zuvor gecachte Daten bei jedem Request neu geladen – Performance-Einbruch.
Ursache
Drei Standardänderungen in Next.js 15:
- fetch: von
force-cachezuno-store - GET Route Handler standardmäßig ohne Cache
- Router Cache standardmäßig deaktiviert
Migration
Variante 1: Cache explizit aktivieren (empfohlen)
// Früher (Next.js 14)
const data = await fetch(url)
// Jetzt (Next.js 15)
const data = await fetch(url, {
cache: 'force-cache',
next: { revalidate: 3600 }
})
Variante 2: use cache
'use cache'
export async function getPostList() {
const posts = await db.posts.findMany()
return posts
}
Variante 3: Router Cache aktivieren
// next.config.js
module.exports = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
Vorher und nachher:
// Next.js 14 – implizites Caching
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts')
// automatisch gecacht
}
// Next.js 15 – explizites Caching
'use cache'
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts', {
cache: 'force-cache',
next: { revalidate: 3600 }
})
}
Empfehlung:
Kein Blind-Migration. Jeden Datenabruf prüfen und bewusst entscheiden, was gecacht wird. Mehr Aufwand kurzfristig – langfristig wissen Sie genau, was gecacht ist und was nicht.
Best Practices und Strategie
Kurz zusammengefasst: Welche Cache-Strategie wann?
Entscheidungsfluss
Wie oft ändern sich Ihre Daten?
|
├─ Kaum (Über-uns, Hilfe)
| → Statische Generierung, kein revalidate
| → Bei Änderung neu deployen
|
├─ Regelmäßig (stündlich, täglich)
| → ISR + zeitbasiertes revalidate
| → export const revalidate = 3600
|
├─ Unregelmäßig (Nutzer veröffentlicht Inhalte)
| → On-Demand revalidate
| → revalidatePath oder revalidateTag
|
└─ Echtzeit (Chat, Live-Daten)
→ Dynamisches Rendering + cache: 'no-store'
→ nicht cachen
Tag-Namenskonvention
Bei revalidateTag empfiehlt sich eine klare Struktur:
Granularität
-
Grob (Masseninvalidierung)
posts– alle Artikelproducts– alle Produkteusers– alle Nutzer
-
Mittel (Kategorie/Status)
posts:published– veröffentlichte Artikelposts:draft– Entwürfeproducts:category:electronics– Elektronik
-
Fein (konkrete Ressource)
post:id:123– Artikel mit ID 123user:profile:456– Profil von Nutzer 456
Namensformat
Namespace entity:type:id:
const post = await fetch(`/api/posts/${id}`, {
next: {
tags: [
'posts',
'posts:published',
`post:id:${id}`
]
}
})
revalidateTag('posts')
revalidateTag('posts:published')
revalidateTag(`post:id:${id}`)
Performance-Tipps
1. Nicht übermäßig cachen
Mehr Cache ist nicht immer besser:
- Inkonsistente Daten
- Schwieriges Debugging
- Speicherverbrauch
Faustregel:
- Personalisierte Daten (Warenkorb, Einstellungen) → nicht cachen
- Öffentliche Daten (Produktliste, Artikelliste) → cachen
- Echtzeitdaten (Bestand, Online-Zähler) → nicht oder nur kurz cachen
2. revalidate-Zeit sinnvoll wählen
revalidate: 1 bedeutet potenziell jede Sekunde Neugenerierung – kein sinnvoller Cache.
Empfehlungen:
- Nachrichten: 30–60 Minuten
- Blog: 1–2 Stunden
- Produktkatalog: 2–4 Stunden
- Statische Seiten: 24 Stunden oder länger
3. stale-while-revalidate nutzen
profile="max" in Next.js 15:
revalidateTag('posts', { profile: 'max' })
Nutzer sehen Cache-Inhalt (schnell), Updates laufen im Hintergrund – gute UX.
4. Cache-Trefferrate überwachen
In .env.local:
NEXT_PRIVATE_DEBUG_CACHE=1
Produktionsserver starten – Ausgabe zeigt Cache-Treffer:
○ GET /blog 200 in 45ms (cache: HIT)
○ GET /about 200 in 12ms (cache: SKIP)
Regelmäßig prüfen, ob die Strategie greift.
Dev vs. Produktion
Wichtig:
Dev (npm run dev) und Produktion (npm start) verhalten sich beim Caching grundlegend unterschiedlich.
| Merkmal | Dev | Produktion |
|---|---|---|
| Data Cache | größtenteils deaktiviert | voll aktiv |
| Full Route Cache | deaktiviert | statische Routen aktiv |
| Request Memoization | aktiv | aktiv |
| Router Cache | aktiv, kurze Dauer | volle Dauer |
Caching richtig testen:
# 1. Produktions-Build
npm run build
# 2. Routentyp in der Ausgabe prüfen
# ○ = statisch, wird gecacht
# ƒ = dynamisch, kein Cache
# 3. Produktionsserver starten
npm start
# 4. Cache-Verhalten testen
# Seite aufrufen, Datenquelle ändern, refreshen
# 5. revalidate testen
# Warten bis revalidate abläuft, erneut aufrufen
Cache-Probleme nicht in Dev debuggen. Das ist die häufigste Falle – viele testen stundenlang in Dev und wundern sich, dass revalidate „nicht funktioniert“.
Fazit
Kernpunkte:
1. Vier Cache-Ebenen verstehen
Nicht verwechseln. Router Cache ist clientseitig, die anderen drei serverseitig. revalidate betrifft vor allem Data Cache und Full Route Cache.
2. Passende revalidate-Methode wählen
- Zeitgesteuert →
export const revalidate = 3600 - Nutzer, eine Seite →
revalidatePath('/page') - Nutzer, mehrere Seiten →
revalidateTag('tag') - Sofort invalidieren →
updateTag('tag')(Next.js 15)
3. In Produktion testen
Dev-Cache ist unzuverlässig. Test: npm run build && npm start.
4. Schicht für Schicht prüfen
Daten aktualisieren sich nicht:
- Router Cache (Client) → Hard Refresh
- Full Route Cache (Server) → revalidatePath
- Data Cache (Server) → revalidateTag
- Datenquelle selbst
5. Explizit vor implizit (Next.js 15)
Caching ist standardmäßig aus – Sie entscheiden aktiv, was gecacht wird. Mehr Klarheit im Code, einfachere Wartung.
Wenn Sie bis hierher gelesen haben, verstehen Sie Next.js Caching ganzheitlich. Beim nächsten „Daten aktualisieren sich nicht“ wissen Sie, wo Sie ansetzen.
Caching ist komplex – aber wenn Sie es beherrschen, ist es eine der stärksten Next.js-Funktionen. Richtig eingesetzt wird Ihre App sehr schnell; falsch eingesetzt graben Sie sich selbst Fallen.
Viel Erfolg mit blitzschneller Performance und wenig Bugs!
Vollständiger Workflow für Next.js Caching
Von den vier Cache-Ebenen bis zur Wahl der revalidate-Methode und Fehlersuche bei nicht aktualisierten Daten
⏱️ Estimated time: 2 hr
- 1
Step 1: Die vier Cache-Ebenen von Next.js verstehen
Vier Cache-Ebenen:
1. Request Memoization (Request-Deduplizierung)
• Gleiche URL wird in einem Request nur einmal abgerufen
• Automatisch, keine Konfiguration nötig
• Lebensdauer: ein einzelner Request
2. Data Cache (fetch-Cache)
• Cache für fetch-Anfragen
• Standard: Next.js 14 cached, 15 nicht
• Konfiguration: cache-Option
3. Full Route Cache (vollständiger Routen-Cache)
• HTML-Cache der gesamten Route
• Statische Seiten werden automatisch gecacht
• Konfiguration: revalidate
4. Router Cache (Client-Routing-Cache)
• Cache bei Client-Navigation
• Automatisch, keine Konfiguration nötig
• Lebensdauer: Sitzungsdauer
Wichtig: Router Cache ist clientseitig, die anderen drei serverseitig. - 2
Step 2: Die passende revalidate-Methode wählen
Drei Methoden:
1. Zeitgesteuert (export const revalidate)
```tsx
export const revalidate = 3600 // nach 3600 Sekunden neu validieren
```
• Geeignet für: zeitgesteuert aktualisierte Inhalte
• Konfiguration: in page.tsx oder layout.tsx
2. Nutzer ausgelöst, einzelne Seite (revalidatePath)
```tsx
import { revalidatePath } from 'next/cache'
revalidatePath('/blog/post-1')
```
• Geeignet für: einzelne Seite nach Nutzeraktion aktualisieren
• Einsatz: in Server Action oder API Route
3. Nutzer ausgelöst, mehrere Seiten (revalidateTag)
```tsx
import { revalidateTag } from 'next/cache'
// Tag beim fetch setzen
fetch(url, { next: { tags: ['posts'] } })
// Tag beim Update invalidieren
revalidateTag('posts')
```
• Geeignet für: mehrere Seiten nach Nutzeraktion aktualisieren
• Einsatz: in Server Action oder API Route
Empfehlung:
• Zeitgesteuert → export const revalidate
• Einzelne Seite → revalidatePath
• Mehrere Seiten → revalidateTag - 3
Step 3: Probleme mit nicht aktualisierten Daten beheben
Prüfreihenfolge:
1. Dev-Umgebung prüfen
• Cache-Verhalten in Dev ist unzuverlässig
• Immer in Produktion testen: npm run build && npm start
2. Router Cache prüfen (Client)
• Hard Refresh (Strg+Umschalt+R)
• Browser-Cache leeren
3. Full Route Cache prüfen (Server)
• revalidatePath zum Leeren
• revalidate-Konfiguration prüfen
4. Data Cache prüfen (Server)
• revalidateTag zum Leeren
• fetch cache-Konfiguration prüfen
5. Datenquelle prüfen
• Wurden die Daten wirklich aktualisiert?
• API-Antwort prüfen
Häufige Fehler:
• In Dev getestet → Produktion nutzen
• Router Cache nicht geleert → Hard Refresh
• revalidate falsch konfiguriert → Konfiguration prüfen
• Next.js 15 cached fetch nicht standardmäßig → cache explizit setzen - 4
Step 4: Cache-Unterschiede zwischen Next.js 14 und 15
Next.js 14:
• fetch wird standardmäßig gecacht (entspricht getStaticProps)
• Explizit deaktivieren: cache: 'no-store'
Next.js 15:
• fetch wird standardmäßig nicht gecacht
• Explizit aktivieren: cache: 'force-cache'
Migrationshinweise:
• Alle fetch-Aufrufe prüfen
• cache-Option explizit setzen
• Cache-Verhalten testen
Code-Beispiel:
```tsx
// Next.js 14 (Standard: Cache)
fetch(url) // automatisch gecacht
// Next.js 15 (Standard: kein Cache)
fetch(url, { cache: 'force-cache' }) // explizit konfigurieren
```
Wichtig: Next.js 15 folgt dem Prinzip „Explizit vor implizit“ – Sie müssen aktiv entscheiden, welche Daten gecacht werden.
FAQ
Wie viele Cache-Ebenen hat Next.js? Was macht jede?
1. Request Memoization (Request-Deduplizierung)
• Gleiche URL wird in einem Request nur einmal abgerufen
• Automatisch, keine Konfiguration nötig
• Lebensdauer: ein einzelner Request
2. Data Cache (fetch-Cache)
• Cache für fetch-Anfragen
• Standard: Next.js 14 cached, 15 nicht
• Konfiguration: cache-Option
3. Full Route Cache (vollständiger Routen-Cache)
• HTML-Cache der gesamten Route
• Statische Seiten werden automatisch gecacht
• Konfiguration: revalidate
4. Router Cache (Client-Routing-Cache)
• Cache bei Client-Navigation
• Automatisch, keine Konfiguration nötig
• Lebensdauer: Sitzungsdauer
Wichtig: Router Cache ist clientseitig, die anderen drei serverseitig. revalidate betrifft vor allem Data Cache und Full Route Cache.
Was ist der Unterschied zwischen revalidatePath, revalidateTag und updateTag?
• Cache für einen bestimmten Pfad leeren
• Geeignet: einzelne Seite nach Nutzeraktion aktualisieren
• Nutzung: revalidatePath('/blog/post-1')
revalidateTag (mehrere Seiten):
• Cache für alle Einträge mit einem Tag leeren
• Geeignet: mehrere Seiten nach Nutzeraktion aktualisieren
• Nutzung: Tag beim fetch setzen, beim Update invalidieren
updateTag (sofortige Invalidierung, Next.js 15):
• Tag sofort als abgelaufen markieren
• Geeignet: Szenarien mit sofortiger Invalidierung
• Nutzung: updateTag('tag')
Empfehlung:
• Einzelne Seite → revalidatePath
• Mehrere Seiten → revalidateTag
• Sofortige Invalidierung → updateTag (Next.js 15)
Hinweis: revalidateTag und updateTag erfordern tags beim fetch.
Warum aktualisieren sich die Daten trotz revalidate nicht?
1. In Dev-Umgebung getestet
• Cache-Verhalten in Dev ist unzuverlässig
• Immer in Produktion testen: npm run build && npm start
2. Router Cache nicht geleert
• Client-Cache noch aktiv
• Hard Refresh (Strg+Umschalt+R)
3. revalidate falsch konfiguriert
• revalidate-Wert prüfen
• Konfigurationsort prüfen
4. Next.js 15 cached fetch nicht standardmäßig
• cache: 'force-cache' explizit setzen
• fetch cache-Konfiguration prüfen
5. Datenquelle selbst nicht aktualisiert
• Wurden die Daten wirklich geändert?
• API-Antwort prüfen
Prüfreihenfolge:
1. Dev-Umgebung prüfen
2. Router Cache (Hard Refresh)
3. Full Route Cache (revalidatePath)
4. Data Cache (revalidateTag)
5. Datenquelle prüfen
Was unterscheidet das Caching in Next.js 14 und 15?
Next.js 14:
• fetch wird standardmäßig gecacht (entspricht getStaticProps)
• Explizit deaktivieren: cache: 'no-store'
• Verhalten: implizites Caching
Next.js 15:
• fetch wird standardmäßig nicht gecacht
• Explizit aktivieren: cache: 'force-cache'
• Verhalten: explizite Konfiguration
Migrationshinweise:
• Alle fetch-Aufrufe prüfen
• cache-Option explizit setzen
• Cache-Verhalten testen
Code-Beispiel:
```tsx
// Next.js 14 (Standard: Cache)
fetch(url) // automatisch gecacht
// Next.js 15 (Standard: kein Cache)
fetch(url, { cache: 'force-cache' }) // explizit konfigurieren
```
Wichtig: Next.js 15 folgt dem Prinzip „Explizit vor implizit“. Das ist ein Breaking Change – bei der Migration beachten.
Wann revalidatePath, wann revalidateTag?
• Geeignet: einzelne Seite nach Nutzeraktion aktualisieren
• Beispiel: Artikeldetailseite nach Bearbeitung
• Nutzung: revalidatePath('/blog/post-1')
revalidateTag (mehrere Seiten):
• Geeignet: mehrere Seiten nach Nutzeraktion aktualisieren
• Beispiel: alle Listen nach neuem Artikel
• Nutzung: Tag beim fetch setzen, beim Update invalidieren
Empfehlung:
• Nur eine Seite → revalidatePath
• Mehrere Seiten → revalidateTag
Code-Beispiel:
```tsx
// Einzelne Seite
revalidatePath('/blog/post-1')
// Mehrere Seiten
fetch(url, { next: { tags: ['posts'] } })
revalidateTag('posts') // alle Einträge mit Tag 'posts' leeren
```
Wichtig: revalidateTag erfordert tags beim fetch und ist flexibler.
Verhalten sich Cache in Dev und Produktion gleich?
Dev-Umgebung:
• Cache-Verhalten instabil
• Möglicherweise kein Cache
• Nicht zum Testen von Caching geeignet
Produktion:
• Cache-Verhalten zuverlässig
• Normaler Cache-Betrieb
• Geeignet zum Testen
Cache testen:
• Immer in Produktion: npm run build && npm start
• Nicht in Dev testen
Häufiger Fehler:
• Cache in Dev getestet → unzuverlässige Ergebnisse
• revalidate „funktioniert nicht“ → oft Umgebungsproblem
Empfehlung: Caching immer in Produktion testen.
17 Min. Lesezeit · Veröffentlicht am: 19. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js State-Management: Zustand vs Jotai – Praxisvergleich und Auswahlhilfe
Redux zu schwergewichtig, Context zu langsam? Dieser Artikel vergleicht Zustand und Jotai in Next.js, liefert eine klare Auswahlhilfe und App-Router-Best-Practices für leichtgewichtige State-Management-Lösungen.
Teil 24 von 51
Nächster
Next.js Bildoptimierung: Der vollständige Leitfaden zur Image-Komponente
Next.js Image-Komponente im Detail: langsame Bilder, Remote-Host-Fehler und Layout-Verschiebung lösen. Next.js 14/15, Praxiscode und Performance-Tipps – oft 60–80 % weniger Bildvolumen.
Teil 26 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen