Incremental Static Regeneration (ISR) ist eine der leistungsstärksten Funktionen von Next.js, die es ermöglicht, statisch generierte Seiten dynamisch im Hintergrund zu aktualisieren. Dies kombiniert die Vorteile von Static Site Generation (SSG) mit der Flexibilität von Server-Side Rendering (SSR), ohne dabei auf Performance oder Skalierbarkeit zu verzichten.
In der Praxis stehen Entwickler:innen jedoch häufig vor Herausforderungen: Wie oft sollte eine Seite regeneriert werden? und Wie lässt sich ISR bei großen Datenmengen effizient implementieren?. In diesem Artikel erklären wir dir die Grundlagen von ISR, geben dir praktische Tipps zur Revalidierung und zeigen Strategien für den Umgang mit großen Datenmengen.
Grundlagen von ISR
Wie funktioniert ISR im App Router?
Im Next.js App Router basiert ISR auf React Server Components und einem flexiblen Revalidierungsmechanismus. Es gibt drei Ansätze, um Seiten oder Inhalte neu zu generieren:
- Zeitbasierte Revalidierung (
revalidate
): Seiten werden nach einem festgelegten Zeitintervall automatisch im Hintergrund neu generiert. - Pfadbasierte Revalidierung (
revalidatePath
): Ermöglicht die manuelle Revalidierung einer spezifischen URL. - Tagbasierte Revalidierung (
revalidateTag
): Ermöglicht die gezielte Revalidierung mehrerer Seiten, die mit einem bestimmten Tag markiert sind.
Zeitbasierte Revalidierung
Mit der revalidate
-Option wird definiert, wie lange eine Seite gecacht bleibt, bevor sie bei der nächsten Anfrage neu generiert wird.
Beispiel: Zeitbasierte Revalidierung
// app/products/page.tsx
import { fetchProducts } from '@/lib/api';
export const revalidate = 60; // ISR: Aktualisierung alle 60 Sekunden
export default async function ProductsPage() {
const products = await fetchProducts();
return (
<div>
<h1>Produkte</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
Pfadbasierte Revalidierung
Die Funktion revalidatePath
kann verwendet werden, um eine spezifische Route oder Seite gezielt neu zu generieren. Dies ist hilfreich, wenn nur bestimmte Inhalte aktualisiert werden müssen, z. B. nach einer Datenänderung.
Beispiel: Pfadbasierte Revalidierung
// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';
export async function POST(request: NextRequest) {
const body = await request.json();
revalidatePath(`/products/${body.productId}`); // Revalidiere nur die spezifische Produktseite
return NextResponse.json({ revalidated: true });
}
Tagbasierte Revalidierung
Die Funktion revalidateTag
ermöglicht es, gecachte Datenquellen gezielt zu invalidieren. Dies ist besonders nützlich, wenn mehrere Seiten oder Komponenten dieselbe Datenquelle verwenden und bei Änderungen automatisch aktualisiert werden sollen.
- Parameter:
tag
: Ein String, der den Cache-Tag der Datenquelle repräsentiert. Dieser muss weniger als 256 Zeichen lang sein und ist case-sensitive.
Beispiel: Tagbasierte Revalidierung
// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidateTag } from 'next/cache';
export async function POST(request: NextRequest) {
const body = await request.json();
revalidateTag('products'); // Invalidiere den Cache der Datenquelle "products"
return NextResponse.json({ revalidated: true });
}
Anwendungsfall:
Stell dir vor, du hast eine Produktseite und ein Dashboard, die beide auf dieselbe Datenquelle zugreifen. Mit revalidateTag('products')
kannst du sicherstellen, dass die neuesten Daten angezeigt werden, ohne jede einzelne Seite manuell revalidieren zu müssen.
Unterschiede zu SSG und SSR
- SSG: Seiten werden ausschließlich während der Build-Phase generiert. Änderungen erfordern einen neuen Build.
- SSR: Seiten werden bei jeder Anfrage neu generiert, was mehr Serverressourcen benötigt.
- ISR: Seiten werden initial statisch generiert und anschließend zeit-, pfad- oder tagbasiert aktualisiert. Dies bietet maximale Flexibilität und Skalierbarkeit.
Wenn du mehr über die Unterschiede zwischen SSR, SSG und ISR erfahren möchtest und wissen willst, welche Render-Methode sich für deinen Anwendungsfall am besten eignet, lies unseren ausführlichen Artikel: SSR, SSG und ISR: Render-Methoden und wann du welche einsetzen solltest.
Vorteile von ISR im App Router
- Flexibilität durch manuelle Revalidierung: Mit
revalidatePath
undrevalidateTag
kannst du gezielt Seiten oder Gruppen von Seiten aktualisieren. - Performance: Inhalte werden aus dem Cache geliefert, wodurch die Ladezeit minimiert wird.
- Skalierbarkeit: ISR reduziert die Serverlast, da Seiten nicht bei jeder Anfrage neu generiert werden müssen.
Wie oft sollte regeneriert werden?
Die Häufigkeit, mit der Inhalte bei Incremental Static Regeneration (ISR) aktualisiert werden, hängt stark von den Anforderungen deiner Anwendung ab. Es gibt keine "Einheitslösung", aber die folgenden Faktoren können dir bei der Entscheidung helfen.
Faktoren für die Revalidierungsfrequenz
-
Art der Daten:
- Statisch: Inhalte wie Blog-Artikel oder Dokumentationen ändern sich selten. Hier reicht eine lange Revalidierungszeit (z. B. einmal am Tag).
- Dynamisch: Produktdaten oder Newsportale ändern sich häufiger und erfordern kürzere Intervalle (z. B. alle 60 Sekunden).
-
Nutzeranforderungen:
- Echtzeit: Wenn Nutzer:innen aktuelle Daten erwarten (z. B. Börsenkurse), ist eine sehr kurze Revalidierungszeit oder eine alternative Lösung wie WebSockets sinnvoll.
- Toleranz für veraltete Inhalte: Inhalte, die nicht sofort aktuell sein müssen, können mit längeren Intervallen gecacht werden.
-
Performance und Serverlast:
- Kürzere Revalidierungszeiten erhöhen die Last auf deinen Servern. Eine längere Cache-Dauer reduziert den Aufwand für die Server.
Praktische Revalidierungsintervalle
Anwendung | Empfohlenes Intervall |
---|---|
Blog-Artikel | 1 Stunde bis 1 Tag |
Produktkatalog | 5 bis 15 Minuten |
Dashboards | 1 bis 5 Minuten |
Newsportale | 1 bis 5 Minuten |
Kombination von Revalidierungsmethoden
Neben zeitbasierten Revalidierungen (revalidate
) kannst du auch revalidatePath
und revalidateTag
verwenden, um gezielt auf Änderungen zu reagieren. Das ist besonders bei dynamischen Datenquellen sinnvoll:
- Zeitbasiert für allgemeine Inhalte:
Verwenderevalidate
, um Standardintervalle für Seiten zu definieren. - Pfadbasiert für spezifische Inhalte:
NutzerevalidatePath
, wenn sich nur eine bestimmte Seite ändern soll (z. B. bei einer Produktaktualisierung). - Tagbasiert für gemeinsame Datenquellen:
MitrevalidateTag
kannst du den Cache von Datenquellen aktualisieren, ohne dass alle zugehörigen Seiten neu generiert werden müssen.
Beispiel: Kombination von Methoden
// app/products/page.tsx
import { fetchProducts } from '@/lib/api';
export const revalidate = 300; // Standard-Revalidierung alle 5 Minuten
export default async function ProductsPage() {
const products = await fetchProducts(); // Produktdaten aus der Datenquelle
return (
<div>
<h1>Produkte</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(request) {
const { updatedProductId } = await request.json();
revalidateTag('products'); // Aktualisiere die Produktdatenquelle
}
ISR bei großen Datenmengen
Die Anwendung von Incremental Static Regeneration (ISR) auf große Datenmengen stellt eine Herausforderung dar, da die Revalidierung von vielen Seiten oder umfangreichen Datenquellen die Performance und Skalierbarkeit beeinflussen kann. Mit den richtigen Strategien kann ISR jedoch effizient auch für große Projekte genutzt werden.
Herausforderungen bei großen Datenmengen
- Hoher Speicherbedarf:
Viele gecachte Seiten oder Datenquellen können den Speicherbedarf des Servers erhöhen. - Lange Revalidierungszeiten:
Wenn bei einer Datenänderung viele Seiten neu generiert werden müssen, kann dies die Revalidierungsdauer verlängern. - Koordination von Änderungen:
Große Datenquellen erfordern eine durchdachte Strategie, um sicherzustellen, dass alle betroffenen Inhalte konsistent aktualisiert werden.
Strategien zur Optimierung
1. Datenpaginierung
Statt alle Daten auf einer Seite zu laden, kann die Paginierung verwendet werden. Dadurch wird die Anzahl der Seiten reduziert, die bei einer Änderung neu generiert werden müssen.
Beispiel: Paginierte Produktliste
// app/products/[page]/page.tsx
import { fetchProductsByPage } from '@/lib/api';
export const revalidate = 600; // Aktualisierung alle 10 Minuten
export default async function ProductsPage({ params }: { params: { page: string } }) {
const products = await fetchProductsByPage(Number(params.page));
return (
<div>
<h1>Produkte - Seite {params.page}</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
2. Selektive Revalidierung mit revalidateTag
Mit revalidateTag
kannst du gezielt Datenquellen aktualisieren, ohne dass alle Seiten neu generiert werden müssen. Dies reduziert die Belastung erheblich.
Beispiel: Selektive Aktualisierung von Produkten
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const { categoryId } = await request.json();
revalidateTag(`category-${categoryId}`); // Aktualisiere nur Produkte der spezifischen Kategorie
return new Response('Revalidated', { status: 200 });
}
3. Priorisierung von Seiten
Nicht alle Seiten müssen gleich behandelt werden. Häufig besuchte Seiten können öfter revalidiert werden, während weniger frequentierte Seiten längere Intervalle haben können.
Beispiel: Unterschiedliche Revalidierungsintervalle
// app/products/page.tsx
export const revalidate = 300; // Beliebte Seiten, z. B. Hauptkategorie
// app/products/details/[id]/page.tsx
export const revalidate = 3600; // Einzelne Produktseiten
Beispiele für große Projekte
- E-Commerce-Website:
- Paginierung: Nutze Paginierung für Produktkataloge.
- Revalidierung nach Tags: Aktualisiere spezifische Kategorien bei Änderungen.
- Newsportal:
- Priorisierte Aktualisierung: Revalidiere Startseitenartikel häufiger als ältere Beiträge.
- Zeit- und tagbasierte Kombination: Nutze
revalidateTag
für Kategorien wie "Politik" oder "Wirtschaft".
Best Practices
- Datenquellen analysieren: Identifiziere, welche Datenquellen und Seiten am meisten Traffic haben und priorisiere deren Revalidierung.
- Kombination von Methoden: Nutze zeit-, pfad- und tagbasierte Revalidierungen, um flexibel und effizient zu bleiben.
- Monitoring und Optimierung: Überwache die Revalidierungsdauer und Serverlast, um Engpässe frühzeitig zu erkennen.
Fehlerbehandlung und Fallbacks
Bei der Nutzung von Incremental Static Regeneration (ISR) ist es wichtig, robust auf Fehler zu reagieren, um Nutzern eine zuverlässige Erfahrung zu bieten. Next.js bietet native Mechanismen, um bei Revalidierungsfehlern den letzten erfolgreichen Cache-Inhalt weiter auszuliefern.
Typische Fehlerquellen
- Fehlgeschlagene API-Aufrufe:
Wenn die Datenquelle nicht erreichbar ist oder ein Netzwerkproblem auftritt. - Ungültige oder fehlende Daten:
Beispielsweise wenn eine API unvollständige oder fehlerhafte Daten zurückgibt. - Serverüberlastung:
Zu viele gleichzeitige Anfragen oder Revalidierungen können Engpässe verursachen.
Fehlerhandling in ISR
Cache-Verhalten bei Fehlern
Wenn während der Revalidierung ein Fehler auftritt, liefert Next.js weiterhin die zuletzt erfolgreich generierten Daten aus dem Cache aus. Dies sorgt dafür, dass die Anwendung auch bei Problemen stabil bleibt.
Next.js versucht bei der nächsten Anfrage automatisch, die Revalidierung erneut auszuführen.
Beispiel: Robustheit durch automatisch gecachte Inhalte
// app/products/page.tsx
import { fetchProducts } from '@/lib/api';
export const revalidate = 300; // Aktualisierung alle 5 Minuten
export default async function ProductsPage() {
const products = await fetchProducts();
return (
<div>
<h1>Produkte</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
);
}
- Wenn ein Fehler auftritt, wird der vorherige Cache-Inhalt ausgeliefert. Nutzer:innen sehen somit weiterhin die letzte Version der Seite.
Erweiterte Strategien zur Fehlerbehandlung
Fehlerlogging
Fehler während der Revalidierung sollten geloggt werden, um sie effizient debuggen zu können. Nutze hierfür Tools wie Sentry oder LogRocket.
Beispiel: Fehlerlogging mit Next.js
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
try {
const { tag } = await request.json();
revalidateTag(tag);
return new Response('Revalidated', { status: 200 });
} catch (error) {
console.error('Fehler bei der Revalidierung:', error);
return new Response('Revalidation failed', { status: 500 });
}
}
Alerting
Nutze Monitoring-Tools, um dein Team bei häufig auftretenden Fehlern zu benachrichtigen. Dies hilft, Probleme frühzeitig zu erkennen und zu beheben.
Vorteile der nativen ISR-Fehlerbehandlung
- Stabile Nutzererfahrung:
Nutzer:innen sehen weiterhin die letzte erfolgreiche Version der Seite, selbst wenn Fehler auftreten. - Automatische Wiederholungsversuche:
Next.js führt die Revalidierung automatisch bei der nächsten Anfrage erneut aus. - Weniger Wartungsaufwand:
Das automatische Cache-Handling von Next.js reduziert die Notwendigkeit für benutzerdefinierte Fallback-Mechanismen.
Best Practices für ISR
Die Implementierung von Incremental Static Regeneration (ISR) erfordert sorgfältige Planung, um maximale Effizienz und Skalierbarkeit zu erreichen. Hier sind einige bewährte Ansätze, die dir dabei helfen:
1. Strukturierung und Priorisierung der Revalidierungslogik
Strukturierung:
- Zeitbasierte Revalidierung: Definiere standardmäßige
revalidate
-Intervalle basierend auf der Aktualität der Inhalte. - Tagbasierte Revalidierung: Verwende
revalidateTag
, um Datenquellen gezielt zu aktualisieren, insbesondere bei großen oder gemeinsam genutzten Daten. - Pfadbasierte Revalidierung: Nutze
revalidatePath
für spezifische Seiten, die unabhängig voneinander aktualisiert werden sollen.
Priorisierung:
- Häufig besuchte Seiten sollten öfter revalidiert werden.
- Weniger wichtige oder selten besuchte Seiten können längere Cache-Intervalle haben, um Serverressourcen zu sparen.
Beispiel: Unterschiedliche Revalidierungsstrategien
// app/products/page.tsx
export const revalidate = 300; // Beliebte Seiten, z. B. Hauptkategorie
// app/products/details/[id]/page.tsx
export const revalidate = 3600; // Selten besuchte Detailseiten
2. Überwachung und Debugging von ISR
Monitoring:
- Performance überwachen: Nutze Tools wie New Relic oder Datadog, um die Revalidierungsdauer und Serverauslastung zu analysieren.
- Fehler erkennen: Verwende Logging-Tools wie Sentry, um Probleme während der Revalidierung zu dokumentieren.
Debugging:
- Nutze die Next.js Debug Logs (
DEBUG=next*
), um Cache- und Revalidierungsprobleme zu untersuchen.
Beispiel: Aktivierung von Debug Logs
DEBUG=next* npm run dev
3. Optimierung der Performance
Datenpaginierung:
Reduziere die Datenmenge, die auf einmal geladen wird, indem du Paginierung implementierst. Dies verringert die Belastung der Server und beschleunigt die Revalidierung.
Cache-Strategien:
- Nutze
revalidateTag
, um nur die notwendigen Datenquellen zu aktualisieren. - Stelle sicher, dass sensible oder selten genutzte Daten aus dem Cache ausgeschlossen werden.
4. Nutzerzentrierte Fallback-Strategien
- Fallback-Inhalte bereitstellen: Vermeide leere Seiten oder unklare Fehlermeldungen. Nutze stattdessen informative Platzhalter.
- Sichtbare Aktualisierungen: Zeige den Nutzern an, wenn neue Inhalte geladen werden, um eine bessere Erfahrung zu bieten.
5. Kombination verschiedener ISR-Methoden
Die besten Ergebnisse erzielst du, wenn du zeit-, pfad- und tagbasierte Revalidierungen kombinierst. Dadurch kannst du Inhalte effizient aktualisieren und die Nutzererfahrung verbessern.
Beispielprojekt: ISR für eine Produktseite
In diesem Beispiel wird eine Produktseite mit Incremental Static Regeneration (ISR) implementiert. Die Seite kombiniert zeit-, pfad- und tagbasierte Revalidierungen, um maximale Effizienz zu gewährleisten.
Projektstruktur
Erstelle die folgenden Dateien:
app/
├── products/
│ ├── [id]/
│ │ ├── page.tsx
│ ├── page.tsx
├── api/
│ ├── revalidate/
│ │ ├── route.ts
lib/
├── api.ts
1. Datenquelle simulieren
Erstelle eine Funktion, um Produktdaten aus einer Datenbank oder API zu laden:
// lib/api.ts
type Product = {
id: string;
name: string;
description: string;
};
const products: Product[] = [
{ id: '1', name: 'Laptop', description: 'Ein leistungsstarker Laptop' },
{ id: '2', name: 'Smartphone', description: 'Ein schnelles Smartphone' },
];
export async function fetchProducts(): Promise<Product[]> {
return products;
}
export async function fetchProductById(id: string): Promise<Product | null> {
return products.find((product) => product.id === id) || null;
}
2. Produktliste mit ISR implementieren
Erstelle eine Seite, die eine Liste von Produkten anzeigt und regelmäßig aktualisiert wird:
// app/products/page.tsx
import { fetchProducts } from '@/lib/api';
export const revalidate = 300; // Aktualisierung alle 5 Minuten
export default async function ProductsPage() {
const products = await fetchProducts();
return (
<div>
<h1>Produkte</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
<a href={`/products/${product.id}`}>{product.name}</a>
</li>
))}
</ul>
</div>
);
}
3. Produktdetailseite mit Pfad- und Tagbasiertem ISR
Erstelle eine Detailseite, die bei Bedarf neu generiert wird:
// app/products/[id]/page.tsx
import { fetchProductById } from '@/lib/api';
export const revalidate = 600; // Aktualisierung alle 10 Minuten
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetchProductById(params.id);
if (!product) {
return <h1>Produkt nicht gefunden</h1>;
}
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
4. Revalidierung per API
Erstelle eine API-Route, um Produkte gezielt zu aktualisieren:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { z } from 'zod';
const RevalidateSchema = z.object({
type: z.enum(['path', 'tag']),
id: z.string().optional(), // `id` ist nur erforderlich, wenn der Typ `path` ist
});
export async function POST(request: Request) {
try {
// Eingabedaten validieren
const body = await request.json();
const parsed = RevalidateSchema.parse(body);
// Revalidierungslogik basierend auf dem Typ
if (parsed.type === 'path' && parsed.id) {
revalidatePath(`/products/${parsed.id}`);
} else if (parsed.type === 'tag') {
revalidateTag('products');
} else {
return new Response('Invalid data', { status: 400 });
}
return new Response('Revalidated', { status: 200 });
} catch (error) {
console.error('Validation error:', error);
return new Response('Invalid input', { status: 400 });
}
}
5. Testen der Lösung
- Zeitbasierte Revalidierung: Warte 5–10 Minuten und überprüfe, ob die Produktliste automatisch aktualisiert wird.
- Pfadbasierte Revalidierung: Sende eine POST-Anfrage an
/api/revalidate
mit demtype: "path"
und überprüfe, ob die Detailseite neu generiert wird. - Tagbasierte Revalidierung: Nutze
type: "tag"
, um alle Produkte neu zu laden.
Ergebnis
Die Kombination von zeit-, pfad- und tagbasierter Revalidierung sorgt für eine performante und dynamische Produktseite. Nutzer:innen profitieren von aktuellen Daten, während die Serverlast minimiert bleibt.
Fazit
Incremental Static Regeneration (ISR) bietet eine leistungsstarke Möglichkeit, Inhalte in Next.js effizient und dynamisch zu aktualisieren, ohne auf die Vorteile statischer Seiten verzichten zu müssen. Mit dem App Router hat Next.js das Potenzial von ISR weiter ausgebaut und ermöglicht flexible Revalidierungsstrategien.
Zusammenfassung:
- Flexibilität durch verschiedene Methoden:
Zeit-, pfad- und tagbasierte Revalidierungen ermöglichen es, sowohl einzelne Seiten als auch ganze Datenquellen gezielt zu aktualisieren. - Robustheit durch Fehlerbehandlung:
Next.js liefert bei Fehlern automatisch den letzten erfolgreichen Cache-Inhalt aus und minimiert so die Auswirkungen von Problemen. - Optimierung bei großen Datenmengen:
Durch Paginierung und selektive Revalidierung kann ISR auch für umfangreiche Projekte wie E-Commerce-Plattformen oder Newsportale effizient eingesetzt werden.
Empfehlungen:
- Strategische Revalidierungsintervalle:
Wähle die Intervalle und Methoden passend zu den Anforderungen deiner Anwendung. Kombiniere zeit-, pfad- und tagbasierte Ansätze für maximale Effizienz. - Datenquelle priorisieren:
NutzerevalidateTag
, um gemeinsam genutzte Datenquellen zu aktualisieren, undrevalidatePath
für spezifische Seiten. - Fehlerhandling implementieren:
Stelle sicher, dass Fehler gut geloggt werden und Nutzer:innen immer eine funktionale Version der Seite sehen.
ISR im App Router bietet dir die Tools, um sowohl Performance als auch Flexibilität zu optimieren. Mit den richtigen Best Practices kannst du deine Anwendung skalierbar und benutzerfreundlich gestalten.