Streaming & Suspense
Nicht warten, bis alles fertig ist
Statt die Seite erst zu schicken, wenn der letzte Ladevorgang durch ist, schickt Next.js sie stückweise. Was fertig ist, sieht der Nutzer sofort.
Eine Server-Komponente, die drei Sekunden lädt, hält die ganze Seite auf. StreamingDie Seite wird stückweise geschickt: Was fertig ist, sieht der Nutzer sofort, langsame Teile kommen nach.→ löst das: Der schnelle Teil geht sofort raus, der langsame kommt nach – und der Nutzer sieht in der Zwischenzeit einen Platzhalter.
Im Restaurant bringt man auch nicht alle Teller gleichzeitig, weil einer davon noch 20 Minuten braucht. Die Vorspeise kommt sofort, der Rest folgt.
Der einfache Weg: loading.tsx
export default function Loading() {
return <p>Lädt …</p>;
} Die Datei loading.tsxDie Anzeige, die Next.js zeigt, solange die Seite darunter noch lädt. Im Hintergrund ist das ein Suspense-Bereich.→ ist nichts anderes als ein <Suspense> um die Seite herum, das Next.js für dich setzt. Das Layout bleibt dabei stehen – nur der Inhaltsbereich zeigt den Platzhalter.
Wenn der Platzhalter grob die Form des späteren Inhalts hat – graue Balken, wo Zeilen kommen –, wirkt der Wechsel ruhiger und die Seite springt nicht. Das ist billiger zu bauen, als es aussieht.
Der feine Weg: Suspense pro Bereich
loading.tsx gilt für die ganze Seite. Oft ist aber nur ein Teil langsam. Dann setzt du SuspenseEin Bereich, der eine Zwischenanzeige zeigt, solange etwas darin noch nicht fertig ist – die Grundlage fürs Streaming.→ gezielt.
export default async function Seite() {
const artikel = await ladeArtikel(); // 50 ms
const empfehlungen = await ladeEmpfehlungen(); // 2000 ms
return (
<>
<Artikel daten={artikel} />
<Empfehlungen daten={empfehlungen} />
</>
);
} export default async function Seite() {
const artikel = await ladeArtikel(); // 50 ms
return (
<>
<Artikel daten={artikel} />
<Suspense fallback={<EmpfehlungsSkelett />}>
<Empfehlungen /> {/* lädt selbst, blockiert nichts */}
</Suspense>
</>
);
} Wichtig ist die Verlagerung: Empfehlungen holt seine Daten jetzt selbst, statt sie als Prop zu bekommen. Nur so kann die äußere Komponente ohne Warten fertig werden.
export async function Empfehlungen() {
const daten = await ladeEmpfehlungen();
return <ul>{daten.map((e) => <li key={e.id}>{e.titel}</li>)}</ul>;
} Wann was
- +
loading.tsx, wenn die ganze Seite von einem Ladevorgang abhängt - +
<Suspense>, wenn nur Teile langsam sind – Empfehlungen, Kommentare, Statistiken - +
<Suspense>außerdem, um einen dynamischen Teil aus einer statischen Seite herauszuhalten
- −
Alles in einen einzigen Suspense-Bereich packen – dann hast du wieder eine Ladeanzeige für alles
- −
Suspense um Dinge, die ohnehin in 20 ms da sind – das flackert nur
- −
Daten oben laden und als Prop hineinreichen: dann wartet die äußere Komponente doch
<Suspense><Liste daten={await laden()} /></Suspense> bringt nichts. Das await steht außerhalb, also wartet die Elternkomponente – und der Suspense-Bereich bekommt nie die Gelegenheit, seinen Platzhalter zu zeigen. Das Laden muss in die eingepackte Komponente.
▸ Genauer erklärt: was technisch passiert optional
Der Server hält die Verbindung offen und schickt die Antwort in Stücken. Zuerst kommt das HTML mit den Platzhaltern, später die nachgereichten Teile samt einer Anweisung, wo sie hingehören.
t=0 ms Grundgerüst + Platzhalter gehen raus
→ Nutzer sieht bereits Kopfzeile, Artikel, Skelett
t=2000 ms Empfehlungen sind fertig
→ Nachtrag geht raus, Platzhalter wird ersetzt
→ kein Sprung, weil das Skelett dieselbe Höhe hatte Der nachgereichte Inhalt ist Teil derselben Antwort, nicht ein späterer Nachladevorgang per JavaScript. Für den Index zählt er also mit – anders als bei clientseitig nachgeladenen Daten.
Partial PrerenderingStatische Hülle sofort ausliefern und die dynamischen Löcher darin nachströmen lassen – beides auf einer Seite.→ treibt den Gedanken weiter: Die statische Hülle wird beim Bauen erzeugt und aus dem Zwischenspeicher ausgeliefert, die dynamischen Löcher darin werden pro Anfrage nachgeströmt. Damit fällt die Entscheidung „statisch oder dynamisch“ für eine Seite ganz weg.
Sitzt das schon?
4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.