React Server Components Performance-Optimierung: Datenabruf und Caching in der Praxis

Wenn Ihre RSC-Seiten noch 300–500 ms TTFB haben, nutzen Sie vermutlich nur etwa 30 % des Performance-Potenzials. Messdaten zeigen: Mit der richtigen Streaming-Architektur sinkt TTFB auf 45 ms – kein Zauber, sondern das Ergebnis aktivierten Streamings bei React Server Components.
Letztes Jahr habe ich einem E-Commerce-Team bei der Optimierung einer Produktdetailseite geholfen und denselben Fehler gemacht. Sie nutzten den Next.js App Router, TTFB blieb stabil bei etwa 380 ms. Die Ursache: verschachtelte Komponenten holten Daten jeweils selbst – ein klassischer Waterfall: Produktinfo wartet auf Bewertungen, Bewertungen auf Preis, Preis auf Lagerprüfung. Neun Sekunden weißer Bildschirm.
Dieser Artikel zeigt, wie Sie das lösen. Ich vergleiche 4 Waterfall-Lösungen, erkläre 5 Cache-APIs und liefere eine direkt kopierbare Konfigurationsvorlage. Von 450 ms auf 45 ms TTFB – oft reichen ein paar Suspense-Grenzen an der richtigen Stelle.
Das Waterfall-Problem: der größte RSC-Performance-Killer
Ein reales Szenario: Sie öffnen eine Produktdetailseite. Zuerst der Produktname, dann 3 Sekunden warten auf den Preis, weitere 5 Sekunden auf die Bewertungen. Nutzererfahrung? Katastrophal.
Das ist das Waterfall-Problem. Verschachtelte Komponenten holen Daten nacheinander statt parallel. Der Datenabruf in React Server Components blockiert standardmäßig synchron – jede Anfrage mit await stoppt das Rendering, sofern Sie nicht Suspense verwenden.
Zwei Formen des Waterfalls
Erste Form: Server-interner Waterfall. Innerhalb einer Seite holt die Elternkomponente Daten, rendert dann die Kindkomponente, die wiederum eigene Daten abruft. Typischer Code:
// Waterfall-Beispiel – problematischer Code
async function ProductPage({ id: string }) {
// Erste Anfrage: 1 Sekunde
const product = await db.getProduct(id);
// Diese Anfragen starten erst nach dem Rendern der Kindkomponenten
return (
<div>
<ProductDetails product={product} />
<ProductPrice id={id} /> {/* intern await getPrice(id), 3 Sekunden */}
<ProductReviews id={id} /> {/* intern await getReviews(id), 5 Sekunden */}
</div>
);
}
// ProductPrice.tsx
async function ProductPrice({ id }) {
const price = await getPrice(id); // läuft erst nach dem Eltern-Render
return <span>{price}</span>;
}
Gesamtzeit? 9 Sekunden. Der Nutzer starrt 9 Sekunden auf eine leere Seite.
Zweite Form: Client-Server-Waterfall. Client-Komponenten fragen den Server an, der wiederum die Datenbank abfragt. Subtiler – React DevTools Profiler zeigt es. Eine Variante des N+1-Problems.
Waterfall erkennen
Öffnen Sie den React DevTools Profiler und zeichnen Sie einen Seitenaufruf auf. Eine treppenförmige Anfrageverteilung auf der Zeitachse – jede Anfrage wartet auf die vorherige – bedeutet Waterfall.
Alternativ: Network-Panel im Browser. Starten die Datenanfragen nacheinander statt gleichzeitig, ist das Problem klar.
Viele Entwickler glauben, RSC bringe automatisch Performance-Gewinn. Laut SitePoint-Bericht 2026 nutzen die meisten Teams nur etwa 30 % des Potenzials – weil Waterfalls unbehandelt bleiben.
Vier Lösungen im Vergleich: von grob bis elegant
Es gibt vier gängige Ansätze – von einfach bis komplex, von grob bis elegant.
Lösung 1: Paralleler Abruf mit Promise.all
Der direkteste Ansatz: alle Anfragen gleichzeitig starten, Promise.all wartet auf alle.
// Lösung 1: Paralleler Abruf mit Promise.all
async function ProductPage({ id: string }) {
// Alle Anfragen gleichzeitig starten
const [product, price, reviews] = await Promise.all([
getProduct(id), // 1 Sekunde
getPrice(id), // 3 Sekunden
getReviews(id), // 5 Sekunden
]);
return (
<div>
<ProductDetails product={product} />
<ProductPriceDisplay price={price} />
<ProductReviewsList reviews={reviews} />
</div>
);
}
Gesamtzeit? 5 Sekunden – bestimmt durch die langsamste Anfrage.
Vorteile: Einfach, geringer Änderungsaufwand.
Nachteile: Nutzer sehen erst nach der langsamsten Anfrage Inhalt. Zudem Datenkopplung: die Elternkomponente muss wissen, welche Daten Kindkomponenten brauchen – verletzt die Unabhängigkeit der Komponenten.
Lösung 2: Suspense-Grenzen isolieren
Datenabhängige Teile mit Suspense umschließen, damit kritische Inhalte zuerst erscheinen.
// Lösung 2: Suspense-Grenzen isolieren
async function ProductPage({ id: string }) {
const product = await getProduct(id); // zuerst kritische Daten
return (
<div>
<ProductDetails product={product} /> {/* nach 1 Sekunde sichtbar */}
{/* Nicht-kritische Teile mit Suspense */}
<Suspense fallback={<PriceSkeleton />}>
<ProductPrice id={id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews id={id} />
</Suspense>
</div>
);
}
Nutzererfahrung: nach 1 Sekunde Produktinfo, nach 3 Sekunden Preis, nach 5 Sekunden Bewertungen.
Vorteile: Kritische Inhalte zuerst, bessere wahrgenommene Performance.
Nachteile: Anfragen starten weiterhin nacheinander. ProductPrice und ProductReviews beginnen erst nach dem Eltern-Render – kein echtes Parallelisieren.
Lösung 3: Promise als Props übergeben
Elternkomponente startet alle Anfragen, übergibt Promises als Props. Kindkomponenten awaiten selbst.
// Lösung 3: Promise-Übergabe-Muster
async function ProductPage({ id: string }) {
// Alle Anfragen sofort starten, ohne await
const productPromise = getProduct(id);
const pricePromise = getPrice(id);
const reviewsPromise = getReviews(id);
// Nur kritische Daten awaiten
const product = await productPromise;
return (
<div>
<ProductDetails product={product} />
<Suspense fallback={<PriceSkeleton />}>
<ProductPrice pricePromise={pricePromise} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews reviewsPromise={reviewsPromise} />
</Suspense>
</div>
);
}
// ProductPrice.tsx – empfängt Promise
async function ProductPrice({ pricePromise }) {
const price = await pricePromise; // nutzt vom Eltern gestartetes Promise
return <span>{price}</span>;
}
Drei Anfragen starten parallel in der Elternkomponente. Kritische Daten nach 1 Sekunde, Preis nach 3, Bewertungen nach 5.
Vorteile: Alle Anfragen parallel, kritische Inhalte zuerst, entkoppelte Daten (Kind empfängt nur Promise).
Nachteile: Komponentenschnittstelle ändern – von id zu Promise.
Lösung 4: React cache() + preload (empfohlen)
React 19 führt die cache()-API ein. Mit dem Preload-Muster die eleganteste Lösung.
// Lösung 4: React cache() + preload
import { cache } from 'react';
// Datenabruf-Funktion mit cache wrappen
const getComments = cache(async (postId: string) => {
return db.getComments(postId);
});
// Preload-Funktion exportieren
export const preloadComments = (id: string) => {
void getComments(id); // ohne await – starten, nicht blockieren
};
// Elternkomponente
async function PostPage({ postId: string }) {
preloadComments(postId); // Bewertungen vorladen
const post = await getPost(postId); // nur kritische Daten awaiten
return (
<div>
<PostContent post={post} />
<Suspense fallback={<CommentsSkeleton />}>
<Comments postId={postId} /> {/* id direkt nutzen, cache wiederverwendet */}
</Suspense>
</div>
);
}
// Comments.tsx – Schnittstelle unverändert
async function Comments({ postId }) {
const comments = await getComments(postId); // nutzt Preload-Promise
return <CommentList comments={comments} />;
}
Prinzip: cache() memoized innerhalb eines Render-Zyklus automatisch. Preload startet die Anfrage ohne zu warten; beim await in der Kindkomponente wird dasselbe Promise wiederverwendet.
Vorteile:
- Komponentenschnittstelle unverändert (weiterhin
id) - Anfragen automatisch memoized, keine Datenkopplung
- Beim Löschen der Kindkomponente wird Preload offensichtlich überflüssig
Nachteile: cache()-Mechanismus verstehen; versteckte Kopplung beachten (Preload beim Löschen von Comments mit entfernen).
Vergleich der vier Lösungen
| Lösung | Gesamtzeit | Kritische Inhalte sichtbar | Datenkopplung | Änderungsaufwand |
|---|---|---|---|---|
| Sequenziell | 9s | 9s | Keine | Keiner |
| Promise.all | 5s | 5s | Ja | Gering |
| Suspense | 5s | 1s | Keine | Gering |
| Promise-Übergabe | 5s | 1s | Entkoppelt | Mittel |
| cache() + preload | 1s | 1s | Keine | Mittel |
Die Wahl hängt vom Team ab: schnelle Migration → Lösung 2, neues Projekt → Lösung 4.
Streaming-Architektur: das Geheimnis von 45 ms TTFB
Klassisches SSR: alle Daten abwarten, vollständiges HTML rendern, einmal an den Browser senden. TTFB (Time to First Byte) = Datenabrufzeit plus Renderzeit.
Konkret: Datenbankabfrage 400 ms, Rendering 50 ms, TTFB etwa 450 ms. Der Nutzer wartet fast eine halbe Sekunde auf weißem Bildschirm.
Wie RSC Streaming den Ablauf verändert
Streaming reduziert nichts – es ändert die Reihenfolge, in der Inhalte beim Nutzer ankommen. Statische Teile sofort, dynamische Teile gestreamt.
// Streaming-Architektur-Beispiel
export default async function Dashboard() {
return (
<Layout> {/* statische Shell, ohne Suspense */}
<Nav /> {/* sofort rendern */}
<Sidebar /> {/* sofort rendern */}
<Suspense fallback={<ChartSkeleton />}>
<DynamicChart /> {/* dynamische Daten, gestreamt */}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable /> {/* dynamische Daten, gestreamt */}
</Suspense>
</Layout>
);
}
Ablauf im Detail:
- T=0 ms: Statische Shell (Layout, Nav, Sidebar) sofort vom CDN-Edge-Cache
- T=30–50 ms: Browser rendert Shell, Skeleton sichtbar
- T=200 ms: DynamicChart-Daten fertig, Inhalt an Suspense-Grenze gestreamt
- T=400 ms: DataTable-Daten fertig, Inhalt gestreamt
TTFB? Etwa 45 ms – Zeit bis die statische Shell gesendet wird.
Rolle von PPR (Partial Prerendering)
PPR ist eine Next.js-15-Funktion, in Next.js 16 standardmäßig aktiv. Statische Teile werden ins CDN vorgerendert, dynamische Teile bleiben gestreamt.
Konfiguration:
// next.config.js – Next.js 15
module.exports = {
experimental: {
ppr: true, // PPR aktivieren
},
};
// next.config.js – Next.js 16 (Vorschau)
module.exports = {
experimental: {
ppr: 'incremental', // schrittweise aktivieren
cacheComponents: true, // neues Cache-Modell
},
};
Mit PPR wird die statische Shell (Navigation, Layout, Skeleton) im CDN vorgehalten. Beim Aufruf liefert das CDN sofort statisches HTML, dynamische Teile ergänzt der Server per Stream.
Designprinzipien für Suspense-Grenzen
Kernprinzip: Ohne Suspense für Streaming-Blöcke behandelt React die gesamte App als einen großen Block.
Richtig:
- Statische Teile ohne Suspense: Navigation, Layout, datenunabhängige Skeletons
- Dynamische Teile mit Suspense: Komponenten mit DB-/API-Abhängigkeit
// Richtiges Beispiel
export default async function Page() {
return (
<>
<Header /> {/* statisch, ohne Wrapper */}
<main>
<Suspense fallback={<HeroSkeleton />}>
<HeroSection /> {/* dynamisch, mit Wrapper */}
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent /> {/* dynamisch, mit Wrapper */}
</Suspense>
</main>
<Footer /> {/* statisch, ohne Wrapper */}
</>
);
}
Falsches Beispiel (gesamte Seite blockiert):
// Falsches Beispiel – Suspense vergessen
export default async function Page() {
const data = await fetchDashboard(); // await blockiert die ganze Seite
return (
<>
<Header />
<Dashboard data={data} />
<Footer />
</>
);
}
Ohne Suspense-Grenzen gilt die ganze Seite als ein Streaming-Block. TTFB bleibt bei 450 ms.
Performance-Vergleich
| Render-Modus | TTFB | LCP | Erläuterung |
|---|---|---|---|
| Klassisches SSR | ~450ms | ~500ms | Wartet auf alle Daten |
| RSC (ohne Suspense) | ~450ms | ~500ms | Entspricht klassischem SSR |
| RSC Streaming | ~45ms | ~200ms | Statische Shell sofort |
| RSC + PPR | ~30ms | ~150ms | Statische Shell im CDN gecacht |
Quelle: SitePoint-Bericht 2026; Messwerte variieren je nach Datenquelle und CDN-Konfiguration.
Fünf Cache-APIs im Überblick
Next.js und React bieten fünf Caching-Mechanismen. Die richtige Wahl spart Aufwand; die falsche erzeugt doppelte Anfragen.
1. fetch cache (am häufigsten)
fetch-Anfragen in Server Components werden automatisch memoized. Gleiche URL und Parameter im selben Render-Zyklus → nur eine Anfrage.
// fetch cache Beispiel
async function ProductCard({ id }) {
// automatisch gecacht, gleiche URL nicht doppelt
const res = await fetch(`https://api.example.com/products/${id}`, {
cache: 'force-cache', // Cache erzwingen (Standard)
next: {
revalidate: 3600, // nach 1 Stunde revalidieren
tags: ['products'], // Tag für revalidateTag
},
});
return <Card data={res.json()} />;
}
async function ProductList() {
// nutzt Cache von oben
const res = await fetch('https://api.example.com/products', {
next: { tags: ['products'] },
});
return <List data={res.json()} />;
}
Optionen:
cache: 'force-cache': Cache bevorzugen (Standard)cache: 'no-store': bei jeder Anfrage neu ladennext.revalidate: zeitgesteuerte Revalidierung (Sekunden)next.tags: Tags für manuelles Refresh via revalidateTag
2. React cache() (neu in React 19)
Cached Funktionsaufrufe. Für DB-Abfragen und eigene Datenabruf-Funktionen.
import { cache } from 'react';
// DB-Abfrage mit cache wrappen
export const getUser = cache(async (id: string) => {
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
return user;
});
// In mehreren Komponenten – automatisch memoized
async function UserProfile({ id }) {
const user = await getUser(id);
return <Profile user={user} />;
}
async function UserStats({ id }) {
const user = await getUser(id); // nutzt Ergebnis von oben
return <Stats user={user} />;
}
Hinweis: cache() gilt nur innerhalb eines Render-Zyklus. Für requestsübergreifendes Caching → unstable_cache.
3. unstable_cache (Next.js 14–15)
Persistenter Cache über Requests hinweg. Für teure Berechnungen und geteilte Daten.
import { unstable_cache } from 'next/cache';
// Funktion mit persistentem Cache
export const getPopularProducts = unstable_cache(
async () => {
const products = await db.getPopularProducts();
return products;
},
['popular-products'], // Cache-Key
{
revalidate: 3600, // nach 1 Stunde revalidieren
tags: ['products', 'popular'], // mehrere Tags
}
);
// Verwendung
async function HomePage() {
const products = await getPopularProducts();
return <ProductGrid products={products} />;
}
Manuelles Refresh:
import { revalidateTag } from 'next/cache';
// in Server Action oder API Route
async function updateProduct() {
await db.updateProduct();
await revalidateTag('products'); // alle products-Tags invalidieren
}
4. use cache (neu in Next.js 16)
Cache-Direktive auf Komponenten- oder Funktionsebene. 'use cache' oben – Ausgabe wird gecacht.
// Funktionsebene 'use cache'
'use cache';
export async function getRecommendations(userId: string) {
return db.getRecommendations(userId);
}
// Komponentenebene 'use cache'
'use cache';
export async function CachedFooter() {
const links = await getFooterLinks();
return <Footer links={links} />;
}
Einsatz: häufig aufgerufene Komponenten, statische Inhalte. Experimentell, in Next.js 16 offiziell unterstützt.
5. revalidatePath / revalidateTag
Manuelles Cache-Refresh.
import { revalidatePath, revalidateTag } from 'next/cache';
// nach Pfad
await revalidatePath('/products'); // alle Caches dieses Pfads
await revalidatePath('/products/[id]', 'page'); // bestimmte Seite
// nach Tag
await revalidateTag('products'); // alle products-Tags
Empfehlung:
- Präzise Steuerung → revalidateTag (empfohlen)
- Batch-Refresh → revalidatePath
Cache-API-Vergleich
| API | Cache-Bereich | Persistent | Einsatz | Version |
|---|---|---|---|---|
| fetch cache | Ein Request | Konfigurierbar | API-Anfragen | Next.js 13+ |
| React cache() | Ein Render-Zyklus | Nein | DB-Abfragen, eigene Funktionen | React 19 |
| unstable_cache | Über Requests | Ja | Teure Berechnungen, geteilte Daten | Next.js 14–15 |
| use cache | Funktion/Komponente | Ja | Häufig aufgerufene Komponenten | Next.js 16 |
| Cache Components | Komponenten-Ausgabe | Ja | mit PPR | Next.js 16 |
Praxis-Konfiguration und typische Fallstricke
Theorie genug – hier die kopierbare Konfiguration.
Vollständige next.config.js
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
// Next.js 15: PPR aktivieren
ppr: true,
// Next.js 16: neues Cache-Modell
// cacheComponents: true, // in stabiler Version aktivieren
},
// Performance
images: {
formats: ['image/avif', 'image/webp'],
},
// Output-Optimierung
output: 'standalone', // für Docker-Deployment
};
export default nextConfig;
Konvention für Preload-Funktionen
Preload erzeugt leicht versteckte Kopplung. Beim Löschen tiefer Kindkomponenten bleibt Preload oft als toter Code.
Empfehlung: Kommentar über der Preload-Funktion mit Zweck und Abhängigkeit.
// comments.ts
import { cache } from 'react';
const getComments = cache(async (postId: string) => {
return db.getComments(postId);
});
/**
* preloadComments: lädt Kommentardaten für Comments-Komponente vor
* Hinweis: beim Löschen von Comments auch preloadComments entfernen
*/
export const preloadComments = (id: string) => {
void getComments(id);
};
export async function Comments({ postId }) {
const comments = await getComments(postId);
return <CommentList comments={comments} />;
}
Typische Fallstricke
Fallstrick 1: Suspense-Grenze vergessen
Symptom: TTFB bleibt bei 450 ms, kein Streaming-Effekt.
Ursache: ohne Suspense gilt die ganze Seite als ein Block.
Lösung: datenabhängige Komponenten mit Suspense umschließen.
// Vorher
async function Page() {
const data = await getData(); // blockiert die ganze Seite
return <Dashboard data={data} />;
}
// Nachher
async function Page() {
return (
<Suspense fallback={<DashboardSkeleton />}>
<Dashboard />
</Suspense>
);
}
Fallstrick 2: Preload ohne Verwendung
Symptom: Anfrage läuft, Daten werden nicht genutzt – verschwendete Ressourcen.
Ursache: Kindkomponente gelöscht, Preload bleibt.
Lösung: Preload mit Komponente löschen oder per Kommentar verknüpfen.
Fallstrick 3: Cache-Tag-Konflikte
Symptom: revalidateTag invalidiert zu viel.
Ursache: unterschiedliche Daten teilen denselben Tag.
Lösung: pro Geschäftsbereich eigene Tags.
// Falsch
await fetch(url, { next: { tags: ['data'] } }); // alles mit 'data'
await revalidateTag('data'); // alles invalidiert
// Richtig
await fetch(productsUrl, { next: { tags: ['products'] } });
await fetch(usersUrl, { next: { tags: ['users'] } });
await revalidateTag('products'); // nur products
Fallstrick 4: fetch cache und React cache gemischt
Symptom: dieselben Daten werden zweimal abgerufen.
Ursache: fetch mit 'no-store', React cache() kann nicht wiederverwenden.
Lösung: fetch mit force-cache oder Standard, damit React cache() greift.
// Falsch
const data1 = await fetch(url, { cache: 'no-store' }); // kein Cache
const data2 = await getData(); // React cache, aber keine Wiederverwendung
// Richtig
const data1 = await fetch(url); // Standard force-cache
const data2 = await getData(); // kann wiederverwenden
Debugging-Tools
- React DevTools Profiler: Rendering aufzeichnen, Waterfall erkennen
- Next.js-Analyse:
next build --experimental-debugfür Build-Analyse - Chrome DevTools: Network für Anfrage-Timing, Performance für Render-Zeitpunkt
Kernmetriken:
- TTFB: Time to First Byte, Ziel < 100 ms
- LCP: Largest Contentful Paint, Ziel < 2,5 s
- CLS: Cumulative Layout Shift, Ziel < 0,1
Migrations-Empfehlung
Von bestehendem SSR zu RSC-Streaming:
- Waterfall identifizieren: Profiler nutzen, sequenzielle Abrufe finden
- Suspense-Grenzen setzen: datenabhängige Komponenten umschließen, kritische Pfade zuerst
- Preload ergänzen: cache() + preload für tiefe Komponenten
- Cache konfigurieren: fetch-Tags für präzises Refresh
Schrittweise migrieren, nicht alles auf einmal. Zuerst langsamste Seiten, Gewinn messen, dann ausweiten.
Zusammenfassung
Kern in drei Schritten: Waterfall erkennen, Lösung wählen, Streaming-Architektur konfigurieren.
Waterfalls erkennen Sie leicht – verschachtelte Komponenten awaiten nacheinander, treppenförmige Verteilung auf der Zeitachse. Vier Lösungen für unterschiedliche Szenarien: schnelle Migration → Suspense-Grenzen, neues Projekt → React cache() + preload.
Entscheidend ist die Platzierung der Suspense-Grenzen. Statische Teile (Navigation, Layout) ohne Wrapper, dynamische Teile (datenabhängige Komponenten) mit Wrapper. Vergessen Sie das, blockiert die ganze Seite weiter.
Messbarer Gewinn: TTFB von 450 ms auf 45 ms – Faktor 10. Dazwischen liegen oft nur ein paar Suspense-Grenzen und eine Preload-Funktion.
Öffnen Sie Ihr Next.js-Projekt und prüfen Sie verschachtelte unabhängige Datenabrufe. Liegt TTFB noch über 300 ms, umschließen Sie kritische Inhalte mit Suspense. Ihre Nutzer merken den Unterschied sofort.
Referenzen
- Data Fetching Patterns and Best Practices — Next.js Official Documentation
- React Server Components Streaming Performance Guide 2026 — SitePoint, 2026-02
- Avoiding Server Component Waterfall Fetching with React 19 cache() — Aurora Scharff, 2025-02
- Avoiding Waterfalls in React Server Components — Akhila Ariyachandra, 2026-01
- Functions: revalidatePath — Next.js Official Documentation
- Functions: revalidateTag — Next.js Official Documentation
- Partial Prerendering (PPR) — Next.js Official Documentation
- React v19 Release — React Official Blog
React Server Components Performance-Optimierung
Vollständige Optimierungsschritte von der Waterfall-Erkennung bis zur Streaming-Architektur
⏱️ Estimated time: 60 min
- 1
Step 1: Waterfall-Problem identifizieren
React DevTools Profiler für Seitenaufruf nutzen:
• Chrome DevTools öffnen, Profiler-Tab wählen
• Aufzeichnen starten, Seite neu laden, warten bis fertig
• Anfrageverteilung auf der Zeitachse prüfen
• Treppenmuster = Waterfall-Problem
• Prüfen, ob TTFB 300 ms überschreitet - 2
Step 2: Lösung wählen
Je nach Team-Situation:
• Schnelle Migration: Lösung 2 (Suspense-Grenzen)
• Neues Projekt: Lösung 4 (React cache() + preload)
• Datenkopplung akzeptabel: Lösung 1 (Promise.all)
• Schnittstelle unverändert: Lösung 4 (empfohlen) - 3
Step 3: Suspense-Grenzen setzen
Datenabhängige Komponenten mit Suspense umschließen:
• Statische Teile ohne Wrapper (Navigation, Layout)
• Dynamische Teile mit Wrapper (datenabhängige Komponenten)
• Passende Fallback-Skeletons bereitstellen
• Kritische Pfade zuerst - 4
Step 4: React cache() + preload konfigurieren
React-19-cache()-API nutzen:
• Datenabruf-Funktionen mit cache wrappen
• Preload-Funktion exportieren, ohne await
• Kommentare für Abhängigkeiten setzen
• Preload beim Löschen der Komponente entfernen - 5
Step 5: Cache-Strategie konfigurieren
Passende Cache-API wählen:
• fetch cache: API-Anfragen (am häufigsten)
• React cache(): Datenbankabfragen
• unstable_cache: requestübergreifendes Teilen
• Tags für präzises Refresh setzen - 6
Step 6: Performance-Gewinn messen
Optimierung verifizieren:
• TTFB-Ziel: < 100 ms
• LCP-Ziel: < 2,5 s
• CLS-Ziel: < 0,1
• Vorher/Nachher-Vergleich der Messdaten
FAQ
Was ist das Waterfall-Problem bei React Server Components?
Wie wähle ich zwischen den vier Waterfall-Lösungen?
• Promise.all: Einfach, schneller Fix, aber Datenkopplung
• Suspense-Grenzen: Kritische Inhalte zuerst, gut für Migration
• Promise-Übergabe: Alle Anfragen parallel, wenn Schnittstellen änderbar
• React cache() + preload: Eleganteste Lösung für neue Projekte (empfohlen)
Wo soll ich Suspense-Grenzen platzieren?
Was ist der Unterschied zwischen den fünf Cache-APIs?
• fetch cache: API-Anfragen, auto-memoized (Next.js 13+)
• React cache(): DB-Abfragen, Cache pro Render-Zyklus (React 19)
• unstable_cache: Persistent über Requests, teure Berechnungen (Next.js 14–15)
• use cache: Funktions-/Komponenten-Cache (Next.js 16)
• revalidatePath/Tag: Manuelles Cache-Refresh
Wie messe ich den Erfolg der RSC-Performance-Optimierung?
Was ist PPR (Partial Prerendering)?
Welche typischen Cache-Konfigurationsfehler gibt es?
• Suspense-Grenze vergessen: Ganze Seite blockiert, TTFB unverändert
• Preload ohne Nutzung: Komponente gelöscht, Preload bleibt, verschwendete Anfragen
• Cache-Tag-Konflikte: revalidateTag invalidiert zu viel
• fetch cache und React cache gemischt: no-store verhindert Wiederverwendung
13 Min. Lesezeit · Veröffentlicht am: 13. Mai 2026 · 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 App Router + shadcn/ui: Server- und Client-Komponenten richtig mischen
Praxisleitfaden zum korrekten Mischen von Server Components und Client Components im Next.js App Router – inkl. shadcn/ui-Integration, Datenfluss, typische Fehler und Performance-Tipps
Teil 46 von 51
Nächster
NextAuth.js Einstieg: Credentials-Login und Session-Management – Komplett-Guide
NextAuth.js wirkt komplex? Dieser Artikel konzentriert sich auf Credentials-Login, die Wahl JWT vs. Session, mit vollständigen Codebeispielen und Fallstricken – ideal für Next.js-Entwickler, die schnell ein Authentifizierungssystem aufsetzen wollen.
Teil 48 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