Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Zur Übersicht
Kern 11 Min Lesezeit

Streaming & Suspense

Nicht warten, bis alles fertig ist

Baut auf: Daten laden
In einem Satz

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.

Bildlich gesprochen

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

app/blog/loading.tsx
export default function Loading() {
  return <p>Lädt …</p>;
}
Mehr braucht es nicht. Next.js zeigt das, solange page.tsx im selben Ordner noch arbeitet.

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.

Tipp Ein Skelett schlägt einen Spinner

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.

alles wartet auf die Empfehlungen
export default async function Seite() {
  const artikel = await ladeArtikel();          // 50 ms
  const empfehlungen = await ladeEmpfehlungen(); // 2000 ms

  return (
    <>
      <Artikel daten={artikel} />
      <Empfehlungen daten={empfehlungen} />
    </>
  );
}
nur der langsame Teil wartet
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.

app/Empfehlungen.tsx
export async function Empfehlungen() {
  const daten = await ladeEmpfehlungen();
  return <ul>{daten.map((e) => <li key={e.id}>{e.titel}</li>)}</ul>;
}

Wann was

Nimm es, wenn …
  • +

    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

Lass es, wenn …
  • 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

Stolperstein Der häufigste Denkfehler

<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.

ablauf.txt
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
Gut zu wissen Auch Suchmaschinen bekommen alles

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.

Tipp Ausblick: Partial Prerendering

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.

Hirefullstack

Ihr braucht React-Verstärkung im Team?

Wir bauen seit Jahren React- und Next.js-Anwendungen für Kunden in ganz Deutschland – als einzelner Experte, als Verstärkung fürs Bestandsteam oder als komplettes Scrum-Team.

Projekt besprechen →