Factory.ai

Open-Source Wikis

/

React

/

React

/

Architecture

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| ReactCore

The 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.js and is wired up in packages/react-dom/src/server/.
  • Static prerender — the prerender/resume family 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

  • react may not import from react-reconciler. The opposite direction is also forbidden — they communicate through ReactSharedInternals (packages/react/src/ReactSharedInternalsClient.js).
  • Renderers (react-dom-bindings, react-native-renderer, ...) import react-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.

Architecture – React wiki | Factory