pattern

Streaming Suspense Boundary

also called Suspense Boundary Placement, Out Of Order Flush Boundary

The point in a server-rendered tree where the response may flush without its data, trading a visible placeholder for an earlier first byte - and where it is drawn decides whether the page arrives fast or arrives and then moves.

streaming-ssrtime-to-first-bytelayout-shiftplaceholdersrendering

A product page needs six data sources. Five answer in about 40 ms. The recommendations service answers in 900 ms at the 99th percentile. Rendered as one unit, every user waits for the slowest source: time to first byte is 900 ms and nothing is on screen until then.

Streaming server rendering lets the server emit the markup it has, send a placeholder for the parts it does not, and flush the real markup later in the same response. The shell can paint at 40 ms. Whether that is an improvement depends entirely on where the boundaries are drawn, and the common mistake is drawing one around the content the user came for.

Why it matters

An HTTP response is a stream and browsers parse it incrementally. A suspended subtree is emitted as a placeholder plus a small script; when the server resolves that subtree it writes the real markup further down the same response and the script swaps it in. Nothing is re-requested.

The cost appears at the swap. If the replaced region is above the fold and its height changes, that is a layout shift under the reader's eyes, and good cumulative layout shift is 0.1 or less at the 75th percentile — a budget one unreserved region exhausts alone. Streaming converts a latency problem into a stability problem, and only reserved space converts it back.

Implementation patterns

  • Draw boundaries around slow and non-critical. Never around the largest contentful paint element, or the metric is measured on the swap, later than the unstreamed version.
  • Reserve the final height of every streamed region. This single discipline is what makes streaming safe, and it is the step teams skip.
  • One boundary per independent slow source, not one per component: a tree with 30 boundaries spends more on placeholders and reflows than it saves.
  • Give each boundary a deadline. If data has not arrived in, say, 500 ms, flush the fallback permanently so a hung dependency cannot hold the response open.
  • Decide status and critical headers before the first flush. Once bytes are out you cannot send a 404, a redirect or a Set-Cookie.

Industry example

The technique generalises the pagelet-flushing approach published by a large social network in 2010, which split a page into independently flushed fragments to get content in front of users before the slowest query returned. Modern streaming renderers implement the same idea with out-of-order flushing.

It pays best on a news organisation's article page at election-night peak: the body available immediately from cache, with live results arriving behind their own boundaries. It pays worst on a dashboard whose single slow query is the content, where streaming adds a spinner and changes nothing a user cares about.

Failure scenarios

  • A boundary around the hero. The shell paints a spinner where the headline belongs, so largest contentful paint lands on the swapped-in content and the streamed page scores worse than the blocking one.
  • Unreserved heights. Each arriving region pushes the page down; layout shift goes from 0.02 to 0.3.
  • Status after flush. The product does not exist, but the 200 was already sent, so you serve a 200 with an error region in it.
  • A boundary whose data never arrives. The response is held open, the connection pool drains, and the user stares at a placeholder with no error.

Trade-offs

Choose Gains Pays
Streaming with boundaries first byte set by the fastest source, not the slowest placeholder markup, reflow per boundary, a layout-shift budget to defend, no post-flush status changes
Render as one unit one coherent paint, full control of status and headers first byte equals the slowest dependency for every user

Streaming also makes a response multi-stage, so "the page was slow" becomes a question about which boundary: per-boundary telemetry comes first.

When not to use it

If every data source answers in tens of milliseconds, streaming adds machinery for no gain: render in one pass. If the route is fully cacheable at the edge, a cached complete document beats a streamed uncached one, because a cache hit is tens of milliseconds and streaming does nothing to make the origin faster. And if the slow source is the main content rather than a sidebar, fix the source, pre-render it, or cache it.

Interview question

Q: "You add streaming to a product page and time to first byte drops from 900 ms to 60 ms, while cumulative layout shift rises from 0.02 to 0.28 and conversion is flat. What happened, what do you change, and how would you have caught it before release?"

What a strong answer covers: unreserved placeholder heights as the mechanism; the boundary possibly wrapping the LCP element; reserving space and moving the boundary below the fold; per-boundary shift attribution; and that a latency win paid for with a stability loss can be net negative.

Quick check

Quiz: Why can a streamed page score worse on largest contentful paint than the blocking version it replaced? — If the boundary wraps the LCP element, the metric is measured on the swapped-in markup, which arrives later than the unstreamed page would have painted it.

Flashcard: What single discipline makes streaming safe? — Reserving the final height of every streamed region, so the swap fills a gap instead of displacing content the reader is already looking at.