Say a dashboard's user profile loads fast but its analytics loads slowly. If you wait for analytics before rendering the whole page, the user just stares at a blank screen. Streaming sends the ready parts first and fills in the slow part later.
The mental model
A Suspense boundary wraps an async component with a fallback. The server ships the page shell first, then streams in updates as each component's data finishes loading. loading.tsx is great for a whole route, while Suspense is more precise for a smaller piece within a page. If the fallback is a skeleton that matches the size of the incoming UI — rather than just a line of loading text — you get less layout shift.
Let's build it together
import { Suspense } from "react";
async function SlowNotes() {
const notes = await getSlowNotes();
return notes.map((note) => <article key={note.id}>{note.title}</article>);
}
export default function NotesPage() {
return (
<main>
<h1>မှတ်စုများ</h1>
<Suspense fallback={<p>မှတ်စုစာရင်း ဖွင့်နေသည်…</p>}>
<SlowNotes />
</Suspense>
</main>
);
}How the code works
NotesPage's heading renders instantly, and only SlowNotes waits inside Suspense. Since the promise isn't awaited in the parent, streaming can actually happen.
The heading appears first, followed by a loading fallback, and then the note list fills in.5-Minute Try-It
Write a page with a profile and an activity list, and stream only the activity list with Suspense.
Next.js — Fetching Data — Next.js