Fizz Server Internals
Fizz is the renderer-agnostic server core in react-server: it turns React elements into render tasks, segments, Suspense boundaries, and streamed output. Its main work loop is performWork, which retries pending tasks and flushes completed queues.
The core is paired with a renderer-specific configuration. The DOM fork re-exports ReactFizzConfigDOM, while the server package supplies the rendering algorithm, class-component behavior, task scheduling, and development diagnostics.
Sources: packages/react-server/src/ReactFizzServer.js:5594-5650, packages/react-server/src/ReactFizzServer.js:6175-6368, packages/react-server/src/forks/ReactFizzConfig.dom.js:9-17, packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js:137-152
Core concepts
Host configuration
A host configuration defines how the generic server renderer represents and emits output for a particular target; the DOM fork exports ReactFizzConfigDOM, while the legacy fork exports ReactFizzConfigDOMLegacy.
Sources: packages/react-server/src/forks/ReactFizzConfig.dom.js:9-17, packages/react-server/src/forks/ReactFizzConfig.dom-legacy.js:9-17
Request
A request is the render-wide state object containing callbacks, resumable state, render state, and the root task and segment structures that Fizz advances.
Sources: packages/react-server/src/ReactFizzServer.js:601-667
Task
A task is a unit of work holding the current node plus rendering context, Suspense ownership, key-path information, and the component stack used for diagnostics.
Sources: packages/react-server/src/ReactFizzServer.js:931-988
Segment
A segment is a pending or completed output region with chunks, children, formatting context, and an optional Suspense boundary.
Sources: packages/react-server/src/ReactFizzServer.js:1049-1070
Component stack
A component stack is a linked chain of ComponentStackNode values that describes rendered component types and, in development, their owners and debug stacks.
Fizz tracks several independent states for Suspense boundaries and segments:
| State | Plain meaning |
|---|---|
PENDING | Work is still outstanding. |
COMPLETED | The work finished successfully. |
FLUSHED | The output has been emitted. |
ABORTED | The work was stopped. |
ERRORED | The work ended with an error. |
CLIENT_RENDERED | The client must render the boundary. |
POSTPONED | The work was deferred for later resumption. |
These states control whether Fizz queues, flushes, retries, or client-renders output.
Sources: packages/react-server/src/ReactFizzServer.js:1284-1302, packages/react-server/src/ReactFizzServer.js:1240-1282, 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 the core is separated from the DOM configuration
The react-server fork imports the Request type from ReactFizzServer, then exports the DOM host configuration and browser console configuration; it also fixes isWorkLoopExternallyDriven and request-storage behavior for that target. The Node fork uses the same DOM configuration but enables AsyncLocalStorage through supportsRequestStorage and requestStorage.
The legacy DOM fork instead exports ReactFizzConfigDOMLegacy, whose doctypeChunk is intentionally empty, while its other resumable and hoistable state types come from ReactFizzConfigDOM. This boundary keeps DOM-specific serialization and output decisions outside the generic task and component machinery.
Evidence
The important boundary is that createRequest and performWork belong to the core, while the DOM fork supplies the exported host configuration consumed by that core.
Sources: packages/react-server/src/forks/ReactFizzConfig.dom.js:9-17, packages/react-server/src/forks/ReactFizzConfig.dom-node.js:10-21, packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js:137-152, packages/react-server/src/ReactFizzServer.js:601-667, packages/react-server/src/ReactFizzServer.js:5594-5650
How a class component renders without a DOM instance
Fizz class rendering begins when renderElement tests shouldConstruct; class-like types go to renderClassComponent, while function types go to renderFunctionComponent. The test is structural: shouldConstruct returns whether the component prototype has isReactComponent.
Before mounting, resolveClassComponentProps removes ref from the props object and applies defaultProps. constructClassInstance then reads context, calls new ctor(props, context), and returns the JavaScript class instance. This is the key distinction from client mounting: the instance is created for rendering and lifecycle compatibility, not as a DOM-backed mount target.
mountClassInstance assigns the updater, props, and initial state, creates an internal update record, and stores it on the instance. It explicitly does not initialize refs because the server will not resolve them. Context is supplied either through contextType, disabled legacy context, or the masked legacy context passed by the task.
Server lifecycle behavior still matters because lifecycle code can change state before render. mountClassInstance applies getDerivedStateFromProps; for eligible legacy lifecycles it calls callComponentWillMount and then processUpdateQueue. callComponentWillMount detects direct state mutation and converts it into enqueueReplaceState, while processUpdateQueue applies queued partial states and assigns the resulting state.
Evidence
- element-dispatchpackages/react-server/src/ReactFizzServer.js:3064
- class-rendererpackages/react-server/src/ReactFizzServer.js:2514
- class-rendererpackages/react-server/src/ReactFizzServer.js:2604
- constructorpackages/react-server/src/ReactFizzClassComponent.js:170
- mount-preparationpackages/react-server/src/ReactFizzClassComponent.js:626
The call order explains why setState from UNSAFE_componentWillMount can affect server markup: the lifecycle queues an update, and the mount path processes that queue before rendering continues. A server test demonstrates the result without requiring a DOM: UNSAFE_componentWillMount calls setState, and the rendered static markup contains the updated text.
Sources: packages/react-server/src/ReactFizzServer.js:3064-3201, packages/react-server/src/ReactFizzServer.js:2480-2482, packages/react-server/src/ReactFizzServer.js:2569-2602, packages/react-server/src/ReactFizzClassComponent.js:170-311, packages/react-server/src/ReactFizzClassComponent.js:626-703, packages/react-server/src/ReactFizzClassComponent.js:544-584, packages/react-server/src/ReactFizzClassComponent.js:586-623, packages/react-dom/src/tests/ReactServerRendering-test.js:298-309
How warnings reconstruct a component stack
Fizz maintains diagnostic context directly on each task. pushComponentStack reads an element’s type, owner, and debug stack, optionally adds server-component debug information, and stores a new stack node created by createComponentStackFromType. renderNodeDestructive pushes this frame before retryNode and restores the previous frame afterward.
The stack is therefore a linked render-time structure, not a fiber traversal. getStackByComponentStackNode walks each node’s parent pointer and formats its type through describeComponentStackByType. That formatter distinguishes built-ins, class components, function components, forward refs, memo components, and lazy components; class detection again uses shouldConstruct.
Owner information is handled separately in development. getOwnerStackByComponentStackNodeInDev follows each node’s owner, formatting either server-component debug stacks or client-component stacks, and appends owner frames only when an owner exists. getThrownInfo delays this work with a lazy componentStack property, so expensive stack generation happens only when the diagnostic is read.
When a recoverable error reaches a Suspense boundary, encodeErrorForBoundary stores the message, error stack, and generated component stack on the boundary in development. Ordinary recoverable errors are reported through logRecoverableError, which calls the request’s onError callback and returns its string digest. This gives warnings and boundary diagnostics useful component context without requiring the client reconciler’s fiber tree.
Sources: packages/react-server/src/ReactFizzServer.js:1240-1282, packages/react-server/src/ReactFizzServer.js:3475-3503, packages/react-server/src/ReactFizzComponentStack.js:109-124, packages/react-server/src/ReactFizzComponentStack.js:50-107, packages/react-server/src/ReactFizzComponentStack.js:133-201, packages/react-server/src/ReactFizzServer.js:1323-1340, packages/react-server/src/ReactFizzServer.js:1342-1381, packages/react-server/src/ReactFizzServer.js:1383-1416
How it connects
The public server entry points create or resume requests, then the core schedules work and streams output; the broader entry-point behavior is covered in Server Rendering (Fizz). createPrerenderRequest delegates to createRequest and enables postponed-hole tracking, while resumeRequest rebuilds either a root render task or a replay task from postponed state.
The DOM-specific side is the host configuration exported by react-dom-bindings; its legacy variant also supplies the empty doctype behavior. The general host-config contract is described in ReactDOM Host Config.
After tasks complete, finishedTask marks root or boundary work ready, and flushCompletedQueues emits the completed root segment and boundary queues to the destination. Suspense output can be client-rendered, pending, or completed based on boundary status in flushSegment.
Sources: packages/react-server/src/ReactFizzServer.js:670-704, packages/react-server/src/ReactFizzServer.js:706-800, packages/react-server/src/forks/ReactFizzConfig.dom.js:9-17, packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js:142-152, packages/react-server/src/ReactFizzServer.js:5179-5325, packages/react-server/src/ReactFizzServer.js:6175-6368, packages/react-server/src/ReactFizzServer.js:5850-6003
Key takeaways
react-serverowns Fizz’s request, task, class-component, Suspense, and diagnostic machinery; DOM forks export the host configuration.- Class components use ordinary JavaScript instances, lifecycle methods, state queues, and context; they do not require a DOM instance.
pushComponentStackbuilds linkedComponentStackNodevalues during rendering, and stack formatting walks those links instead of a fiber tree.- Development errors attach lazily generated component stacks to Suspense boundaries and error information.
- Completed tasks become segments and boundaries that the streaming queues flush to the destination.
Sources: packages/react-server/src/forks/ReactFizzConfig.dom.js:9-17, packages/react-server/src/ReactFizzClassComponent.js:170-311, packages/react-server/src/ReactFizzClassComponent.js:626-703, packages/react-server/src/ReactFizzServer.js:1240-1282, packages/react-server/src/ReactFizzComponentStack.js:109-124, packages/react-server/src/ReactFizzServer.js:1323-1340, packages/react-server/src/ReactFizzServer.js:1342-1381, packages/react-server/src/ReactFizzServer.js:5179-5325, packages/react-server/src/ReactFizzServer.js:6175-6368