The streamstreaming & suspense

What if the page didn’t wait for its slowest part?

It doesn’t have to. The static shell ships first, and each slow part leaves a fallback in its place. When a part finishes on the server, its HTML streams down the same response and takes the fallback’s place. The fast parts never wait for the slow ones, whatever they sit next to.

The three rows below are slow on purpose, with their delays printed on them. Each one is a Server Component that finishes on the server and streams in when it is done. Await everything and nothing appears until the slowest is back. Add a fallback and a placeholder takes the blank’s place, but the rows still arrive together. Wrap each part and the order holds: the shell, then the rows, fastest first. The response view shows the same run as the server sent it: one response, held open, a chunk per boundary.

working
shell3 placeholders+0 ms
01s2s

a boundary per row · three arrivals · each row waits only for itself

stage.tsx · the three arrangements
// the same three calls in every arrangement. only the boundary moves.
const rows = [
  { label: "a quick database query",           delayMs:  400 },
  { label: "a third-party API with opinions",  delayMs: 1100 },
  { label: "the legacy service nobody dares",  delayMs: 1900 },
];

// 1 — fetch it all first, then render
<Suspense fallback={null}>
  <GroupRows />   {/* awaits all three, then returns all three */}
</Suspense>

// 2 — add a fallback. this is what loading.tsx is.
<Suspense fallback={<GroupPending />}>
  <GroupRows />   {/* same component, same wait */}
</Suspense>

// 3 — wrap each part
{rows.map((row) => (
  <Suspense fallback={<PendingRow {...row} />} key={row.label}>
    <SlowRow {...row} />   {/* awaits its own work, and nobody else's */}
  </Suspense>
))}