Streaming

Chunked HTML

With Suspense, the server sends the response in chunks using HTTP chunked transfer. The browser renders each chunk as it arrives; React inserts the streamed parts in place with tiny inline scripts.

Example

Watch the chunks arrive with curl's no-buffer flag:

curl -N localhost:3000/products/42

Example output

<!DOCTYPE html><html>...<h1>Redmi Note 14</h1>
<!--$?--><template id="B:0"></template><p>Loading reviews...</p><!--/$-->
...                                  (0.4 s later)
<div hidden id="S:0"><section>Reviews (4.3★)</section></div>
<script>$RC("B:0","S:0")</script>

Things That Break Streaming

  • A proxy or CDN that buffers responses (nginx: set X-Accel-Buffering: no or proxy_buffering off for Next.js).
  • Compression middleware that waits for the full body.
  • Awaiting everything at the top of the page instead of inside Suspense boundaries.

Order of Completion

Example

Streamed parts arrive in the order their data finishes. A slow part does not block faster parts after it:

const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
const log: string[] = [];

async function boundary(name: string, ms: number) {
  await sleep(ms);
  log.push(name + ' (' + ms + ' ms)');
}

async function main() {
  log.push('shell');
  await Promise.all([boundary('recommendations', 90), boundary('delivery', 30), boundary('reviews', 60)]);
  log.push('end');
  console.log(log.join('\n'));
}
main();

Output

shell
delivery (30 ms)
reviews (60 ms)
recommendations (90 ms)
end