facebook/react
Architecture
React's runtime is split into three large layers: a thin public API (react), a renderer-agnostic reconciler (react-reconciler), and one or more host renderers (react-dom, react-native-renderer, react-art, react-test-renderer, react-noop-renderer). The renderers plug into the reconciler through a host config — a struct of imperative methods that describe how the host platform creates, mutates, and destroys nodes. A separate cooperative scheduler package controls when work runs.
On top of that core, React Server Components add a second runtime: a Flight server (react-server) that streams a JSON-ish wire format, and a family of Flight clients (react-server-dom-*) that hydrate that stream into client React elements. The whole thing is built by a custom Rollup pipeline that emits per-package, per-channel, per-environment bundles.
High-level component diagram
graph TD
App[Your component tree] -->|createElement / JSX| ReactCore[react: createElement, hooks dispatcher]
ReactCore -->|elements| Reconciler[react-reconciler: fiber engine]
Reconciler -->|host config| DOM[react-dom-bindings: ReactFiberConfigDOM]
Reconciler -->|host config| Native[react-native-renderer: Fabric/Paper config]
Reconciler -->|host config| Test[react-test-renderer]
Reconciler -->|host config| Noop[react-noop-renderer]
Reconciler -->|host config| Art[react-art]
Reconciler -->|schedule callback| Scheduler[scheduler]
Scheduler -->|MessageChannel / postTask| Browser[Host event loop]
subgraph Server Components
Flight[react-server: Flight writer] -->|streamed payload| FlightClient[react-server-dom-*: webpack/parcel/turbopack/esm/unbundled]
end
FlightClient -->|virtual elements| ReactCoreThe three runtime layers
1. react (the public surface)
packages/react/src/ReactClient.js and packages/react/src/ReactServer.js are the two top-level entry points: one for the "client" half of the dual-runtime split, one for the "react-server" half. They re-export createElement, the built-in hooks (useState, useEffect, useMemo, ...), Children, forwardRef, memo, lazy, Suspense, and the misc surface area documented at react.dev.
react itself does very little work. The hooks it exports are thin wrappers that call into a current dispatcher (packages/react/src/ReactSharedInternalsClient.js). Each renderer installs its own dispatcher implementation while it is rendering, and React swaps it in and out per fiber. This is how the same useState import can mean "a real reducer-backed hook" inside a real render and "throw, you can't call this here" outside one.
2. react-reconciler (the fiber engine)
The reconciler is the brain. It owns the fiber tree (a doubly-linked alternate-pair representation of the component tree), the lanes model (priority bitmask), the work loop, Suspense boundaries, transitions, hydration, hooks state, and the commit phase. It is renderer-agnostic: every interaction with the host platform goes through the host config in packages/react-reconciler/src/ReactFiberConfig.js (which is replaced with ReactFiberConfigDOM, ReactFiberConfigFabric, ReactFiberConfigART, ReactFiberConfigTest, etc. at build time via the fork resolver in packages/react-reconciler/src/forks/).
The largest source files are all here:
| File | Lines | Responsibility |
|---|---|---|
packages/react-reconciler/src/ReactFiberWorkLoop.js |
~5,000 | The work loop: scheduling, render phase, commit phase, error handling |
packages/react-reconciler/src/ReactFiberHooks.js |
~4,500 | All built-in hook implementations + dispatcher tables |
packages/react-reconciler/src/ReactFiberCommitWork.js |
~5,000 | Walking effect lists during commit |
packages/react-reconciler/src/ReactFiberBeginWork.js |
~4,500 | The "begin" half of the render phase — one switch per fiber tag |
packages/react-reconciler/src/ReactFiberCompleteWork.js |
~2,000 | The "complete" half — bubble flags up, prepare host instances |
packages/react-reconciler/src/ReactChildFiber.js |
~1,900 | Children diffing (the part that produces a "key" warning) |
packages/react-reconciler/src/ReactFiberLane.js |
~1,200 | Priority bitmask: lanes, lane sets, getNextLanes, etc. |
See packages/react-reconciler for a deeper tour.
3. Host renderers
Each renderer's job is to provide a host config and re-export a small public API on top of react-reconciler. The DOM renderer's host config is packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js; React Native Fabric's lives in packages/react-native-renderer/src/ReactFiberConfigFabric.js; ART's in packages/react-art/src/ReactFiberConfigART.js. The test renderer (packages/react-test-renderer) and noop renderer (packages/react-noop-renderer) exist primarily for the React team's own tests but are exported.
There are also two server-side render paths inside react-dom:
- Fizz — the modern streaming SSR runtime. Code lives in
packages/react-server/src/ReactFizzServer.jsand is wired up inpackages/react-dom/src/server/. - Static prerender — the
prerender/resumefamily used for partial pre-rendering with web/node streams. Same Fizz core, different driver.
The scheduler
packages/scheduler is a tiny cooperative scheduler. It uses MessageChannel (or postTask where available) to yield to the browser between work units. The reconciler calls into it via Scheduler.unstable_scheduleCallback (re-exported from packages/scheduler/src/forks/). The scheduler is intentionally separable: it is published as scheduler on npm and is used by some non-React projects.
Data flow: a render in react-dom
sequenceDiagram
participant App as User code
participant DOMClient as react-dom (createRoot)
participant Reconciler as react-reconciler
participant Sched as scheduler
participant DOM as DOM
App->>DOMClient: root.render(<App/>)
DOMClient->>Reconciler: updateContainer(element, root)
Reconciler->>Reconciler: scheduleUpdateOnFiber, mark lanes
Reconciler->>Sched: scheduleCallback(NormalPriority, performWorkOnRoot)
Sched-->>Reconciler: invoke performWorkOnRoot (next tick)
loop work loop
Reconciler->>Reconciler: beginWork (per fiber)
Reconciler->>Reconciler: completeWork (per fiber)
Reconciler->>Sched: shouldYield()? — yield if expired
end
Reconciler->>DOM: commit phase: appendChild, setAttribute, etc.
Reconciler-->>App: useEffect callbacks fire (passive effects)The render phase is interruptible: between any two fibers, shouldYield() from the scheduler can pause the work and resume it later. The commit phase is synchronous and not interruptible.
Server Components / Flight
Server Components are a separate runtime that runs on the server (or build time) and produces a streamed Flight payload — essentially a flattened, ID-keyed log of React elements, server-evaluated values, and pointers to "client references" (modules the bundler must ship to the browser).
graph LR RSC[RSC component tree on server] -->|renderToReadableStream| Writer[react-server: ReactFlightServer] Writer -->|wire format| Network[(network)] Network -->|response| Reader[react-server-dom-*: ReactFlightClient] Reader -->|React.createElement| ClientReact[react on the client] ClientReact -->|hydrate / render| DOM[DOM]
Key code:
packages/react-server/src/ReactFlightServer.js— the writer.packages/react-server-dom-webpack/src/ReactFlightDOMClient*.js— the webpack bindings (similarly for parcel, turbopack, esm, unbundled, fb).packages/react-server/src/ReactFizzServer.js— Fizz, the streaming HTML SSR engine. Server Components feeds Fizz; the two cooperate to produce HTML and Flight in a single pipeline.
See features/server-components for the full pipeline.
Build pipeline (Rollup)
scripts/rollup/build-all-release-channels.js is the entry point invoked by yarn build. It runs scripts/rollup/build.js once per release channel (stable, experimental, www-modern, www-classic, etc.) and per bundle type (UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD, ESM_DEV, ESM_PROD, FB_WWW_DEV, FB_WWW_PROD, RN_OSS_DEV, …). Each combination produces a separate set of files in build/.
The list of bundles is centralized in scripts/rollup/bundles.js. Each entry knows its entry-point file, its bundle types, its global (for UMD), and its dependencies. The flag system in packages/shared/ReactFeatureFlags.js (and the per-channel forks in packages/shared/forks/) is what makes "the same source produces stable + experimental builds" tractable.
graph LR Source[packages/*/src/**] -->|babel + flow strip| Rollup Flags[packages/shared/forks/ReactFeatureFlags.*.js] -->|fork resolver| Rollup Rollup -->|per channel + per type| Build[build/oss-stable, build/oss-experimental, build/facebook-www, ...] Build -->|published| NPM[(npm: react@19.x, react@0.0.0-experimental-..., ...)]
Release channels and feature flags are explained in detail in reference/configuration.
Repository layering rules
reactmay not import fromreact-reconciler. The opposite direction is also forbidden — they communicate throughReactSharedInternals(packages/react/src/ReactSharedInternalsClient.js).- Renderers (
react-dom-bindings,react-native-renderer, ...) importreact-reconciler/inline.dom, etc. — fork-resolved aliases that pin the host config. - Cross-package internal imports go through
packages/shared/(e.g.packages/shared/ReactSymbols.js,packages/shared/ReactFeatureFlags.js). A custom ESLint rule,scripts/eslint-rules/no-cross-fork-imports.js, enforces this.
Beyond those rules, anything the build system needs (entry-point shape, package.json exports, NPM publishing) is described in reference.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.
Previous
React
Next
Getting started