SYSTEM Cited by 1 source
Confluence Streaming SSR¶
Atlassian Confluence's server-side rendering tier, rebuilt in 2025–26 on
top of React 18's renderToPipeableStream to emit HTML progressively
at <Suspense> boundaries instead of holding the response until the
whole tree rendered. A NodeJS transform pipeline sequences per-chunk
state injection before markup, force-flushes compression, and signals
intermediate nginx proxies not to buffer — so the chunks actually reach
the browser in real time. Delivered ~40% First Contentful Paint
improvement as one of several changes that halved Confluence's p90
page-load time over 12 months.
Pipeline (server)¶
renderToPipeableStream(<App />)produces a stream of HTML chunks as each ready<Suspense>boundary resolves.- State-injection transform (object mode): listens on
getServerStream('data-stream')data emissions, buffers them while React is mid-chunk, flushes them before the corresponding markup on eachsetImmediatetick. (Ordering matters — hydration fails if state arrives after markup.) See concepts/react-hydration. - Page-annotation transforms: start/end markers, script-preload
<link>tags, metrics markers. Also object mode; search windows bounded so regex doesn't re-scan already-emitted content. compressionmiddleware, force-flushed onsetImmediateafter each chunk — otherwise it holds partial compressed output.- Response header
X-Accel-Buffering: noso the upstream nginx proxy does not re-buffer. See concepts/head-of-line-buffering.
Pipeline (client)¶
- Initial HTML contains pending boundaries as
<!--$?-->+<template id="B:N">. React's inline-JS runtime replaces placeholders as chunks arrive. - Hydration runs per-boundary using state already injected ahead of the markup. See patterns/suspense-boundary.
- JS bundle preload driven by asset prediction (from prior-render component IDs) and refined mid-stream by component-ID metadata pushed inline. See patterns/asset-preload-prediction.
Known pitfalls caught in production¶
- Context-change hydration re-render (React 18): a ready Suspense
boundary plus a context change discards and re-renders the subtree —
once per boundary. Confluence mitigated with
unstable_scheduleHydrationto raise hydration priority; React 19 fixes it properly. A leaky state-management library compounded this with per-render listener growth → CPU regression. - Buffer-mode regex transforms: buffer↔string round-trip was a
dominant cost on large pages; fixed by running transforms in
objectModethroughout. - Intermediate buffering silently squashes chunks into blobs; must disable at every proxy + compression layer.
Numbers¶
- ~40% improvement in First Contentful Paint from streaming SSR alone.
- ~50% cut in time-to-interaction from the asset-preload-prediction feedback loop.
- Rollout measured FCP, TTVC (target VC90), TTI, hydration success rate at p50/p90/p99 via A/B test over "several weeks".