Server Rendering (Fizz)
Fizz is the server renderer behind react-dom's streaming entry points, including renderToPipeableStream and renderToReadableStream. It turns a React component tree into HTML chunks and sends those chunks through a destination rather than mutating a browser DOM.
It exists so the server can send usable HTML before every component is ready. Suspense boundaries divide the output into independently tracked work, allowing the shell and fallbacks to flush while slower content completes later.
Sources: packages/react-dom/src/server/ReactDOMFizzServerNode.js:135-170, packages/react-dom/src/server/ReactDOMFizzServerNode.js:211-297, packages/react-server/src/ReactFizzServer.js:1049-1070
Core concepts
Request
A request is the per-render object that owns callbacks, pending work, output state, and the destination receiving bytes. createRequest constructs it, creates the root segment and root task, and queues that task for work.
Sources: packages/react-server/src/ReactFizzServer.js:601-667
Segment
A segment is a buffered piece of output with its own chunks, children, format context, and optional Suspense boundary. createPendingSegment initializes it as PENDING until the renderer completes it.
Sources: packages/react-server/src/ReactFizzServer.js:1049-1070
Suspense boundary
A Suspense boundary is the server-side decision point between completed content, a fallback, and client rendering. flushSegment selects the corresponding wrapper and output path from the boundary status.
Sources: packages/react-server/src/ReactFizzServer.js:5850-5932
Format context
A format context carries the current HTML namespace, insertion mode, selected form value, tag scope, and view-transition context. createRootFormatContext chooses HTML, SVG, or MathML mode for the root.
Renderer configuration
Renderer configuration is the host-specific layer that converts generic rendering operations into output chunks. The DOM configuration validates tags, writes attributes and elements, escapes text, and emits Suspense markers.
Fizz tracks the main task and boundary states below; these states determine whether work can be flushed, retried, or replaced with client rendering.
| State | Plain meaning | Used for |
|---|---|---|
PENDING | Work is still incomplete. | Newly created segments and boundaries. |
COMPLETED | Work finished and can be queued for output. | Finished boundaries and segments. |
FLUSHED | Output has been written. | Stream progress tracking. |
ABORTED | Work was stopped. | Aborted tasks and segments. |
ERRORED | Work encountered an error. | Error handling state. |
POSTPONED | Work is deferred for later completion. | Prerender and resume flows. |
Sources: packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4214-4225, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4232-4332, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:1180-1195, packages/react-server/src/ReactFizzServer.js:344, packages/react-server/src/ReactFizzServer.js:345, packages/react-server/src/ReactFizzServer.js:346, packages/react-server/src/ReactFizzServer.js:347, packages/react-server/src/ReactFizzServer.js:348, packages/react-server/src/ReactFizzServer.js:349
How a tree becomes an HTML stream
The server first creates a request with the input children, DOM render state, resumable state, root format context, and lifecycle callbacks. It creates a root segment, marks it as already unblocked, creates a root render task, and places that task in request.pingedTasks.
startWork schedules performWork on a microtask and later schedules early-preload work. performWork installs the server hooks and async dispatcher, makes the request current, retries each pinged task, and flushes completed queues when a destination is attached.
The task renderer walks each node through renderNodeDestructive, which records the current node and child index before calling retryNode. retryNode resolves React elements and calls renderElement; renderElement dispatches function components, class components, host elements, fragments, lists, and Suspense boundaries to their specialized renderers.
For a host element, the DOM configuration starts a tag chunk, processes props, adds view-transition attributes when enabled, writes dangerouslySetInnerHTML or string children, and returns any non-string children for continued traversal. Text nodes are escaped before being appended to the segment's chunk list.
The following architecture shows the hand-off from request creation to scheduled work and destination flushing.
Evidence
- server-entrypackages/react-dom/src/server/ReactDOMFizzServerNode.js:362
- render-requestpackages/react-server/src/ReactFizzServer.js:601
- work-looppackages/react-server/src/ReactFizzServer.js:5594
- html-configpackages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4232
- stream-destinationpackages/react-server/src/ReactFizzServer.js:6464
Sources: packages/react-server/src/ReactFizzServer.js:601-667, packages/react-server/src/ReactFizzServer.js:6370-6413, packages/react-server/src/ReactFizzServer.js:5594-5649, packages/react-server/src/ReactFizzServer.js:3475-3503, packages/react-server/src/ReactFizzServer.js:3505-3587, packages/react-server/src/ReactFizzServer.js:3064-3143, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4012-4052, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4232-4332, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:1180-1195
How Suspense streams the shell first
When a component suspends, renderNode catches the thrown thenable, creates a suspended replay task, attaches ping handlers, restores rendering context, and returns without losing the task's position. This is what lets the request continue with other renderable work.
A boundary's tasks are counted independently. When the final task completes, finishedTask changes a pending boundary to COMPLETED, queues completed segments when their parent has flushed, and schedules completed boundaries for later emission.
During flushing, flushSegment chooses among three paths. A CLIENT_RENDERED boundary emits a client-rendered wrapper and its fallback; a non-completed boundary receives a pending wrapper and its fallback; a completed boundary may emit its content inline or outline it for progressive delivery when size or suspense-related content makes that preferable.
The shell cannot flush while root tasks remain pending, and it also waits for the preamble to be ready. Once those conditions are met, flushCompletedQueues writes the preamble, flushes the completed root segment, writes the completed-root instruction, and then processes client-rendered and completed boundaries.
This sequence follows the web-stream path from resumed rendering to shell delivery.
Diagram omitted: missing
onShellReady resolves the readable stream before all work is necessarily complete; each stream pull invokes startFlowing, while cancellation stops flowing and calls abort. The fake writable adapts the core's writable destination to a ReadableStream controller by encoding strings and calling enqueue; ending the destination calls close.
Sources: packages/react-server/src/ReactFizzServer.js:4312-4388, packages/react-server/src/ReactFizzServer.js:5179-5227, packages/react-server/src/ReactFizzServer.js:5850-5949, packages/react-server/src/ReactFizzServer.js:6175-6250, packages/react-dom/src/server/ReactDOMFizzServerNode.js:375-399, packages/react-dom/src/server/ReactDOMFizzServerNode.js:172-199, packages/react-dom/src/server/ReactDOMFizzServerNode.js:186-188
What the renderer configuration indirection does
The core renderer operates on generic segments, boundaries, requests, destinations, and flush queues. The DOM configuration supplies the concrete formatting operations: it validates tag names, caches opening and closing tag chunks, escapes text and attribute values, and writes HTML-specific element forms.
The separation also carries format state through the tree. getChildFormatContext changes insertion modes for html, head, svg, math, tables, and form controls, while the Suspense-specific helpers derive fallback and content contexts from the parent.
This is the server-rendering analogue of a host-config boundary: the generic Fizz algorithm decides when work is ready, while the DOM-specific layer decides how that work is represented as HTML chunks and instruction markers. The repository also describes react-server host configuration as a module-level shim supplied through a bundle wrapper, which explains why the core can remain renderer-agnostic while a renderer provides its own operations.
Sources: packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4214-4225, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:4437-4444, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:1370-1388, packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js:857-950, packages/react-server/src/forks/ReactServerStreamConfig.custom.js:10-23
How it connects
Server output becomes the input for Hydration, where the client reconciler attaches React behavior to existing HTML. The Suspense markers and streamed replacement instructions connect to Fizz Streaming Instruction Set, whose browser runtime processes completed and client-rendered boundaries.
The generic server core is documented further in Fizz Server Internals, while the DOM-specific formatting and resource behavior connects to ReactDOM Host Config and Resource Hoisting & Preloading (Float). Server component payloads may share the HTML stream boundary described in Server Components (Flight).
Key takeaways
- Fizz creates a request, root segment, and root task, then schedules work independently from the output destination.
- Host elements become escaped HTML chunks through the DOM renderer configuration.
- Suspended work becomes retryable tasks, so the shell and fallbacks can stream before slower content.
- Completed boundaries are queued and flushed after their parent output makes them addressable.
- The web-stream adapter exposes shell output through
ReadableStreamwhile preserving Fizz's generic writable destination.