Die 4 Cache-Layer erklärt und entmystifiziert
Next.js cacht auf vier Ebenen gleichzeitig – und viele Entwickler wissen nicht, welcher Cache warum anschlägt. Request Memoization, Data Cache, Full Route Cache und Router Cache interagieren miteinander und führen zu verwirrenden Ergebnissen, wenn man ihre Regeln nicht kennt.
Inhaltsverzeichnis
- 1. Warum Next.js Caching so komplex erscheint
- 2. Request Memoization: Deduplizierung innerhalb eines Renders
- 3. Data Cache: persistenter Server-seitiger Cache
- 4. Full Route Cache: statische HTML-Ausgabe auf dem Server
- 5. Router Cache: Client-seitiger Prefetch-Speicher
- 6. Revalidierung: zeit- und ereignisbasiert
- 7. Caching gezielt deaktivieren
- 8. Caching-Verhalten debuggen
- 9. Die 4 Cache-Layer im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Next.js Caching so komplex erscheint
Next.js Caching ist eines der mächtigsten Features des Frameworks – und gleichzeitig die häufigste Quelle von Verwirrung und unerwartetem Verhalten. Entwickler berichten von Seiten, die nach Datenbankänderungen noch stundenlang alte Daten anzeigen, oder von API-Calls, die im Produktions-Build nicht stattfinden, obwohl sie im Entwicklungsmodus problemlos laufen. Der Grund: Next.js cacht auf vier verschiedenen Ebenen gleichzeitig, jede mit eigenen Regeln, eigener Laufzeit und eigenen Invalidierungsmechanismen.
Das Verständnis des Next.js Caching-Systems ist keine optionale Vertiefung – es ist eine Voraussetzung dafür, dass Anwendungen korrekt und performant funktionieren. Wer nicht weiß, welcher Cache-Layer aktiv ist, wird bei Debugging-Sessions stundenlang im Dunkeln tappen. Die gute Nachricht: Sobald man die vier Layer kennt und versteht, wie sie zusammenwirken, wird das System vorhersehbar und man kann es gezielt für maximale Performance nutzen.
Die vier Cache-Layer unterscheiden sich nach Lebenszeit, Speicherort und Invalidierungsauslöser: Request Memoization lebt nur für einen einzigen Render-Durchlauf. Der Data Cache übersteht Server-Neustarts. Der Full Route Cache liegt auf dem Server als statische HTML-Datei. Der Router Cache lebt im Speicher des Browsers des Nutzers. Ein HTTP-Request zu einem Next.js-Server durchläuft potenziell alle vier Ebenen, bevor eine Datenbankabfrage stattfindet.
2. Request Memoization: Deduplizierung innerhalb eines Renders
Request Memoization ist die erste und kurzlebigste Cache-Ebene im Next.js Caching-System. Sie dedupliziert identische fetch()-Aufrufe innerhalb eines einzigen Server-Renders. Wenn zwei verschiedene Server Components auf derselben Seite dieselbe URL mit denselben Optionen abrufen, führt Next.js nur einen einzigen HTTP-Request durch und gibt das gecachte Ergebnis an beide Komponenten zurück. Diese Memoization gilt nur für die Laufzeit eines einzigen Requests – nach dem Render wird der Speicher geleert.
Der praktische Nutzen ist erheblich: Ohne Request Memoization müsste man Daten stets von oben durch die Komponentenhierarchie als Props durchreichen, um doppelte API-Calls zu vermeiden. Mit Memoization kann jede Komponente ihre Daten direkt und unabhängig abrufen, ohne Koordination mit Geschwister- oder Eltern-Komponenten. Das ermöglicht die saubere Co-Location von Data-Fetching und Komponente – ein Grundprinzip des App Routers.
// Request Memoization: both components call the same URL,
// but Next.js fires only ONE HTTP request per render pass.
async function getProduct(id: string) {
// This fetch is automatically deduplicated within the same render
const res = await fetch(`https://api.example.com/products/${id}`)
return res.json()
}
// ProductTitle and ProductPrice can both call getProduct(id)
// independently — Next.js merges them into a single network request
async function ProductTitle({ id }: { id: string }) {
const product = await getProduct(id) // fires HTTP request
return <h1>{product.name}</h1>
}
async function ProductPrice({ id }: { id: string }) {
const product = await getProduct(id) // returns memoized result, no new HTTP
return <p>{product.price} €</p>
}
// Data Cache: time-based revalidation (ISR-style)
async function getLatestPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }, // revalidate once per hour
})
return res.json()
}
// Data Cache: opt out — always fresh data, no caching
async function getLiveStockLevel(sku: string) {
const res = await fetch(`https://api.example.com/stock/${sku}`, {
cache: 'no-store', // bypass Data Cache completely
})
return res.json()
}
3. Data Cache: persistenter Server-seitiger Cache
Der Data Cache ist die zweite Ebene im Next.js Caching-System und übersteht Server-Neustarts und mehrere Requests. Er speichert die Ergebnisse von fetch()-Aufrufen auf dem Server und gibt gecachte Antworten zurück, ohne den eigentlichen Upstream-Endpunkt zu kontaktieren. Standardmäßig ist der Data Cache aktiv – das bedeutet, dass ein fetch()-Call ohne explizite Cache-Konfiguration die Antwort unbegrenzt cached.
Das ist der häufigste Grund für veraltete Daten nach dem Deployment: Entwickler sehen, dass ihre API-Daten sich nicht aktualisieren, und vermuten einen Bug im Code – in Wirklichkeit liefert der Data Cache eine stundenlang alte gecachte Antwort. Die Lösung: explizite next.revalidate-Optionen für zeitbasierte Invalidierung oder cache: 'no-store' für immer frische Daten. Der Data Cache kann auch programmatisch per revalidatePath() oder revalidateTag() invalidiert werden, wenn sich Daten geändert haben.
Cache-Tags sind besonders mächtig im Next.js Caching-System: Mit next: { tags: ['products'] } markiert man einen Fetch-Call, sodass alle Responses mit diesem Tag per revalidateTag('products') gleichzeitig invalidiert werden – zum Beispiel nach einem Webhook-Call vom CMS oder nach einer Produktbearbeitung im Admin-Panel. Das ermöglicht granulare, ereignisbasierte Cache-Invalidierung ohne unnötige Invalidierung aller gecachten Daten.
4. Full Route Cache: statische HTML-Ausgabe auf dem Server
Der Full Route Cache ist die dritte Ebene und die beeindruckendste Performance-Optimierung im Next.js Caching-System. Next.js rendert statische Routen zur Build-Zeit als HTML und React Server Component Payload und speichert das Ergebnis auf dem Server. Bei einem Request zu einer gecachten Route wird sofort das fertige HTML geliefert – kein Datenbankzugriff, kein Rendering, keine Compute-Zeit. Das ist im Wesentlichen dasselbe wie eine statisch generierte Seite, aber ohne dass man sie manuell als statisch markieren muss.
Eine Route wird automatisch statisch gecacht, wenn sie keine dynamischen Funktionen nutzt: keine cookies(), keine headers(), kein searchParams, kein cache: 'no-store' in Fetch-Calls. Sobald eine dieser dynamischen Funktionen genutzt wird, wechselt Next.js zur dynamischen Rendering-Mode für diese Route und deaktiviert den Full Route Cache. Die Entscheidung zwischen statischem und dynamischem Rendering trifft Next.js zur Build-Zeit automatisch – man kann sie aber mit dem dynamic-Export explizit steuern.
// Full Route Cache: static rendering (default when no dynamic functions)
// Next.js renders this to static HTML at build time
export default async function StaticProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }, // ISR: regenerate every hour
}).then(r => r.json())
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
// Full Route Cache: force dynamic rendering
export const dynamic = 'force-dynamic' // bypass Full Route Cache
export const revalidate = 0 // alternative: same effect
// Dynamic rendering is triggered automatically by:
import { cookies, headers } from 'next/headers'
export default async function DynamicPage() {
// Using cookies() forces dynamic rendering for this route
const cookieStore = await cookies()
const userId = cookieStore.get('userId')?.value
return <p>Logged in as: {userId}</p>
}
// On-demand revalidation via Server Action or Route Handler
import { revalidatePath, revalidateTag } from 'next/cache'
export async function updateProduct(id: string) {
await db.product.update({ where: { id }, data: { ... } })
revalidatePath('/products') // invalidate Full Route Cache for /products
revalidateTag('products') // invalidate all Data Cache entries tagged 'products'
}
5. Router Cache: Client-seitiger Prefetch-Speicher
Der Router Cache ist die vierte und für Endnutzer sichtbarste Ebene im Next.js Caching-System. Er lebt ausschließlich im Browser-Speicher des Nutzers und speichert den React Server Component Payload von Routen, die der Nutzer besucht hat oder die prefetched wurden. Das ermöglicht sofortige Navigation zwischen bekannten Seiten ohne Roundtrip zum Server. Wenn ein Nutzer auf einen Link zeigt, beginnt Next.js im Hintergrund, die Zielseite zu prefetchen und in den Router Cache zu legen.
Der Router Cache hat eine begrenzte Lebensdauer: Statisch gerenderte Routen bleiben 5 Minuten gecacht, dynamisch gerenderte Routen 30 Sekunden. Der Cache wird komplett geleert, wenn der Nutzer die Seite neu lädt, oder selektiv nach einem Server Action-Aufruf. Das führt manchmal zu dem Verhalten, dass Nutzer nach einer Datenbankänderung via Server Action noch kurz alte Daten sehen, bis der Router Cache abläuft oder gezielt invalidiert wird.
Wichtig: Der Router Cache kann nicht direkt aus Server-Code heraus gesteuert werden – er lebt vollständig im Browser. Über router.refresh() aus dem useRouter()-Hook kann eine Client Component den Server dazu bringen, die aktuelle Route neu zu rendern und den Router Cache für diese Route zu aktualisieren. Das ist das Muster für Real-Time-ähnliche Aktualisierungen ohne vollständige Page-Reload.
6. Revalidierung: zeit- und ereignisbasiert
Revalidierung ist der Mechanismus, über den veraltete Caches im Next.js Caching-System aktualisiert werden. Die zeitbasierte Revalidierung (ISR) funktioniert nach dem Stale-While-Revalidate-Prinzip: Wenn eine gecachte Route oder ein gecachtes Datum abgelaufen ist, liefert Next.js zunächst die veraltete gecachte Version (sofortige Antwort), rendert im Hintergrund die neue Version und ersetzt den Cache. Der nächste Request erhält dann die frische Version. Das minimiert Latenz, ohne immer auf frische Daten warten zu müssen.
Die ereignisbasierte Revalidierung per revalidatePath() und revalidateTag() ist mächtiger und präziser. Sie wird aus Server Actions oder Route Handlers aufgerufen – typischerweise nach einer Schreiboperation. revalidateTag('blog-posts') invalidiert alle Data Cache-Einträge, die mit dem Tag blog-posts markiert wurden, egal auf welcher Route sie gecacht sind. Das ermöglicht CMS-Integrationen, bei denen ein Content-Update im Backend sofort alle relevanten Seiten revalidiert – ohne zeitbasierte Verzögerung.
// Tag-based cache invalidation — precise and event-driven
async function getBlogPosts() {
return fetch('https://cms.example.com/posts', {
next: {
revalidate: 3600, // also revalidate after 1 hour as fallback
tags: ['blog-posts'], // mark with tag for on-demand invalidation
},
}).then(r => r.json())
}
// Route Handler for CMS webhook — invalidates tagged cache entries
// POST /api/revalidate?secret=xxx&tag=blog-posts
export async function POST(request: Request) {
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const tag = searchParams.get('tag')
// Security: verify webhook secret
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
if (tag) {
revalidateTag(tag) // invalidate all Data Cache entries with this tag
return Response.json({ revalidated: true, tag })
}
return Response.json({ error: 'No tag provided' }, { status: 400 })
}
7. Caching gezielt deaktivieren
Es gibt Situationen, in denen man das Next.js Caching vollständig oder selektiv deaktivieren möchte: Echtzeit-Dashboards, personalisierten Content, A/B-Tests oder Seiten mit hochfrequenten Datenbankänderungen. cache: 'no-store' in einem Fetch-Call deaktiviert den Data Cache für genau diesen Call. export const dynamic = 'force-dynamic' auf Seitenebene deaktiviert den Full Route Cache und sorgt dafür, dass die Route bei jedem Request neu gerendert wird.
Für die Entwicklungsphase verhält sich das Next.js Caching-System anders als in der Produktion: Im Development-Modus (next dev) ist der Full Route Cache deaktiviert und Routen werden bei jedem Request neu gerendert, damit Änderungen sofort sichtbar sind. Der Data Cache ist im Development ebenfalls deaktiviert. Das erklärt, warum bestimmte Caching-Probleme erst nach dem Deployment auftreten – im Development gab es schlicht keinen Cache.
8. Caching-Verhalten debuggen
Wenn das Next.js Caching-Verhalten unerwartet ist, gibt es mehrere Debugging-Strategien. Die Umgebungsvariable NEXT_PRIVATE_DEBUG_CACHE=1 aktiviert ausführliche Cache-Logs im Server-Output, die zeigen, ob ein Request den Cache trifft (HIT) oder nicht (MISS). Im Build-Output von next build zeigt Next.js für jede Route, ob sie statisch (○), dynamisch (ƒ) oder ISR (⊛) ist – das verrät sofort, welcher Cache-Layer aktiv sein wird.
Für den Data Cache kann man mit unstable_noStore() aus next/cache dynamisches Rendering für eine Komponente erzwingen, ohne den gesamten Fetch-Call anzupassen. Das ist nützlich, um in bestimmten Komponenten den Data Cache zu deaktivieren, während andere Fetch-Calls gecacht bleiben. Ein häufiges Debugging-Muster: Temporär cache: 'no-store' für alle relevanten Fetches setzen, bestätigen dass die Seite die erwarteten Daten zeigt, und dann schrittweise Caching wieder aktivieren um die Quelle des Problems einzugrenzen.
9. Die 4 Cache-Layer im Vergleich
Die vier Ebenen des Next.js Caching-Systems unterscheiden sich fundamental nach Speicherort, Lebensdauer und dem, was sie cachen. Eine klare Übersicht hilft dabei, die richtige Caching-Strategie für jeden Anwendungsfall zu wählen.
| Cache-Layer | Speicherort | Lebensdauer | Invalidierung |
|---|---|---|---|
| Request Memoization | Server, RAM | Ein Render-Durchlauf | Automatisch nach Render |
| Data Cache | Server, persistent | Unbegrenzt (Standard) | revalidate, revalidateTag |
| Full Route Cache | Server, Dateisystem | Bis zum nächsten Build/revalidate | revalidatePath, force-dynamic |
| Router Cache | Browser, RAM | 30s (dynamisch), 5min (statisch) | router.refresh(), Page Reload |
Das Next.js Caching-System ist so designed, dass es ohne Konfiguration maximale Performance liefert. Der Trade-off: Entwickler müssen explizit opt-out durchführen, wenn frische Daten benötigt werden. Das Standardverhalten – aggressives Caching auf allen Ebenen – ist für die meisten öffentlichen Seiten ideal, aber für personalisierte oder hochdynamische Inhalte muss man gezielt eingreifen. Die gute Nachricht: Mit dem Wissen über alle vier Layer ist das Eingreifen präzise und vorhersehbar.
Mironsoft
Next.js Performance, Caching-Strategie und App Router Architektur
Next.js Caching optimieren und richtig einsetzen?
Wir analysieren euer Next.js-Projekt, identifizieren falsch konfiguriertes Caching, optimieren Data Cache und Full Route Cache und implementieren präzise ereignisbasierte Revalidierungs-Strategien.
Caching-Audit
Analyse aller Cache-Layer in eurem Projekt und Identifikation von Performance-Potenzial
ISR & Tags
Zeitbasierte und ereignisbasierte Revalidierungs-Strategien mit CMS-Integration implementieren
Performance
Core Web Vitals und TTFB durch optimale Caching-Konfiguration messbar verbessern
10. Zusammenfassung
Next.js Caching arbeitet auf vier Ebenen: Request Memoization dedupliziert identische Fetches innerhalb eines Renders, der Data Cache speichert API-Antworten persistent auf dem Server, der Full Route Cache speichert statisch gerenderte Seiten als HTML, und der Router Cache beschleunigt Client-seitige Navigation im Browser. Jede Ebene hat eigene Regeln, Lebensdauern und Invalidierungsmechanismen – das System arbeitet standardmäßig mit maximaler Aggressivität, um Performance zu maximieren.
Die wichtigsten Handlungsempfehlungen: Den Data Cache mit next.tags markieren, um präzise Invalidierung per revalidateTag() zu ermöglichen. cache: 'no-store' nur dort einsetzen, wo wirklich immer frische Daten nötig sind. Nach Server Actions revalidatePath() aufrufen, damit der Full Route Cache aktualisiert wird. Und im Development-Modus immer daran denken, dass die Caching-Ebenen dort nicht aktiv sind – Produktions-Tests sind unerlässlich.
Next.js Caching — Die 4 Layer auf einen Blick
Request Memoization
Automatische Deduplizierung identischer fetch()-Calls innerhalb eines Render-Durchlaufs. Kein Setup nötig, immer aktiv.
Data Cache
Persistenter Server-Cache für fetch()-Antworten. next.revalidate für ISR, next.tags + revalidateTag für On-Demand-Invalidierung.
Full Route Cache
Statische HTML-Ausgabe auf dem Server für Routen ohne dynamische Funktionen. revalidatePath() oder force-dynamic zum Steuern.
Router Cache
Client-seitiger Prefetch-Speicher im Browser. 30s (dynamisch), 5min (statisch). router.refresh() für Client-seitige Aktualisierung.