Incremental Static Regeneration (ISR) in der Praxis

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:

  1. Zeitbasierte Revalidierung (revalidate): Seiten werden nach einem festgelegten Zeitintervall automatisch im Hintergrund neu generiert.
  2. Pfadbasierte Revalidierung (revalidatePath): Ermöglicht die manuelle Revalidierung einer spezifischen URL.
  3. 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

  1. Flexibilität durch manuelle Revalidierung: Mit revalidatePath und revalidateTag kannst du gezielt Seiten oder Gruppen von Seiten aktualisieren.
  2. Performance: Inhalte werden aus dem Cache geliefert, wodurch die Ladezeit minimiert wird.
  3. 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

  1. 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).
  2. 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.
  3. 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

AnwendungEmpfohlenes Intervall
Blog-Artikel1 Stunde bis 1 Tag
Produktkatalog5 bis 15 Minuten
Dashboards1 bis 5 Minuten
Newsportale1 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:

  1. Zeitbasiert für allgemeine Inhalte:
    Verwende revalidate, um Standardintervalle für Seiten zu definieren.
  2. Pfadbasiert für spezifische Inhalte:
    Nutze revalidatePath, wenn sich nur eine bestimmte Seite ändern soll (z. B. bei einer Produktaktualisierung).
  3. Tagbasiert für gemeinsame Datenquellen:
    Mit revalidateTag 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

  1. Hoher Speicherbedarf:
    Viele gecachte Seiten oder Datenquellen können den Speicherbedarf des Servers erhöhen.
  2. Lange Revalidierungszeiten:
    Wenn bei einer Datenänderung viele Seiten neu generiert werden müssen, kann dies die Revalidierungsdauer verlängern.
  3. 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

  1. E-Commerce-Website:
    • Paginierung: Nutze Paginierung für Produktkataloge.
    • Revalidierung nach Tags: Aktualisiere spezifische Kategorien bei Änderungen.
  2. 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

  1. Datenquellen analysieren: Identifiziere, welche Datenquellen und Seiten am meisten Traffic haben und priorisiere deren Revalidierung.
  2. Kombination von Methoden: Nutze zeit-, pfad- und tagbasierte Revalidierungen, um flexibel und effizient zu bleiben.
  3. 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

  1. Fehlgeschlagene API-Aufrufe:
    Wenn die Datenquelle nicht erreichbar ist oder ein Netzwerkproblem auftritt.
  2. Ungültige oder fehlende Daten:
    Beispielsweise wenn eine API unvollständige oder fehlerhafte Daten zurückgibt.
  3. 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

  1. Stabile Nutzererfahrung:
    Nutzer:innen sehen weiterhin die letzte erfolgreiche Version der Seite, selbst wenn Fehler auftreten.
  2. Automatische Wiederholungsversuche:
    Next.js führt die Revalidierung automatisch bei der nächsten Anfrage erneut aus.
  3. 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 dem type: "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:

  1. 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.
  2. Datenquelle priorisieren:
    Nutze revalidateTag, um gemeinsam genutzte Datenquellen zu aktualisieren, und revalidatePath für spezifische Seiten.
  3. 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.

Das könnte dich auch interessieren

Wenn du mehr über Next.js und moderne Webentwicklung erfahren möchtest, findest du hier weitere spannende Beiträge, die dir bei deinen Projekten weiterhelfen können.

Bereit für den nächsten Schritt?

Vertiefe dein Wissen mit unseren Next.js-Workshops und werde vom Einsteiger zum Experten. Erhalte praxisnahe Übungen, fertige App-Vorlagen und persönliche Nachbetreuung.