# How React Router Manages Navigation State Internally: The Complete Architecture Guide

> Discover how React Router manages navigation state internally. Learn about its state machine, router instances, and how it updates subscribers for seamless navigation.

- Repository: [Remix/react-router](https://github.com/remix-run/react-router)
- Tags: architecture
- Published: 2026-03-06

---

**React Router centralizes navigation state in a mutable `RouterState` object owned by a router instance created via `createRouter`, which processes history events through a deterministic state machine in [`router.ts`](https://github.com/remix-run/react-router/blob/main/router.ts) that coordinates actions, loaders, fetchers, and blockers before emitting updates to React subscribers.**

Navigation state management in React Router revolves around a single router instance created by `createRouter`. According to the `remix-run/react-router` source code, this architecture isolates all navigation logic—including location tracking, data loading, and transition states—within a framework-agnostic core that communicates with React via a subscription model.

## The RouterState Data Structure

The router maintains a comprehensive **RouterState** object that serves as the single source of truth for the entire navigation lifecycle. This mutable state container describes everything the UI needs to know about the current navigation context:

- **`historyAction`**: The last `Action` (`POP`, `PUSH`, or `REPLACE`) emitted by the history implementation
- **`location`**: The current `Location` object containing pathname, search, hash, state, and key
- **`matches`**: An array of `AgnosticDataRouteMatch` objects produced by `matchRoutes` in [`utils.ts`](https://github.com/remix-run/react-router/blob/main/utils.ts)
- **`initialized`**: Boolean indicating whether the initial data load has completed
- **`renderFallback`**: Controls whether a *HydrateFallback* should render during SSR hydration
- **`navigation`**: A `Navigation` object describing in-flight navigation status (`idle`, `loading`, or `submitting`)
- **`loaderData`**, **`actionData`**, **`errors`**: Data returned from route loaders, mutations, and error boundaries
- **`fetchers`**: A map of active fetcher instances for non-navigation data loading
- **`blockers`**: A map of navigation blockers managing pending navigation interruptions
- **`revalidation`**: Status flag (`idle` or `loading`) for background data revalidation
- **`restoreScrollPosition`** and **`preventScrollReset`**: Flags controlling scroll restoration behavior

## The Navigation State Machine

React Router implements a deterministic state machine in [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts) that processes all navigation events through distinct phases, ensuring predictable transitions between application states.

### History Event Detection

When the underlying **History** implementation (browser, hash, or memory) emits a `POP` event, the router's `listen` callback within the `initialize` function invokes `startNavigation` with the new `Location` and history `action`. This abstraction allows the router to normalize behavior across different environments without direct DOM dependencies.

### Navigation Initialization via startNavigation

The **`startNavigation`** function orchestrates the transition preparation:

- Cancels any in-flight navigation using `AbortController` to prevent race conditions
- Preserves the current scroll position for potential restoration
- Calls **`matchRoutes`** (defined in [`packages/react-router/lib/router/utils.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/utils.ts)) to convert the route configuration into an array of matches for the target location
- Handles edge cases including hash-only changes, 404s, and lazy route discovery
- Constructs a `Request` object and scoped `RouterContextProvider` for the navigation
- Routes mutation submissions to **`handleAction`** and data fetching to **`handleLoaders`**

### Completion via completeNavigation

When all asynchronous operations settle, **`completeNavigation`** executes the state transition:

- Merges new loader data with existing cached data using `mergeLoaderData`
- Updates the browser history (push or replace) except for POP-only revalidations
- Emits a new `RouterState` through **`updateState`**
- Notifies all **`RouterSubscriber`** callbacks (including `<RouterProvider>`), triggering React re-renders with fresh matches and data

## Fetchers and Non-Navigation Data Loading

**Fetchers** enable data loading and submission without changing the URL, essential for optimistic UI and background updates. When `router.fetch(key, routeId, href, opts)` is called in [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts), the router:

1. Creates a fetcher entry in `state.fetchers`
2. Executes loaders or actions through the same pipeline as standard navigation
3. Maintains the fetcher's lifecycle state (`idle`, `loading`, or `submitting`) in the map
4. Automatically cleans up fetcher entries when they return to `idle`

```tsx
import { useFetcher } from "@remix-run/react";

function Comments() {
  const fetcher = useFetcher();

  return (
    <>
      <button onClick={() => fetcher.load("/comments?post=1")}>
        Load comments
      </button>
      {fetcher.state === "loading" && <p>Loading…</p>}
      {fetcher.data && <CommentsList comments={fetcher.data} />}
    </>
  );
}

```

## Navigation Blocking and Interception

The router supports blocking navigation through the **`shouldBlockNavigation`** function. When `navigate` is called, this function checks the `blockerFunctions` map. If a blocker exists, the router stores a **`BlockerBlocked`** entry in state and pauses navigation until the UI calls `proceed` or `reset`, powering features like `usePrompt` for unsaved form warnings.

```tsx
import { useBlocker } from "@remix-run/react";

function FormPage() {
  useBlocker(
    ({ currentLocation, nextLocation }) => {
      return window.confirm("Leave the page?");
    },
    true // block on POP as well
  );
  return <Form>...</Form>;
}

```

## Background Revalidation

The **`router.revalidate()`** method triggers background data refreshing without changing the current location. The process creates a deferred promise, aborts in-flight loads, sets `state.revalidation` to `"loading"`, and calls `startNavigation` with `startUninterruptedRevalidation`. Upon completion, `completeNavigation` clears the flag and resolves the promise, allowing UI components to display loading indicators while preserving current route content.

## Scroll Restoration Management

Scroll restoration is handled through **`enableScrollRestoration`**, which injects callbacks that store positions in `savedScrollPositions`. When `completeNavigation` runs, **`getSavedScrollPosition`** determines whether to restore, keep, or ignore saved positions based on the navigation type and `preventScrollReset` flags. The router never manipulates the DOM directly; instead, it computes scroll targets and lets React components execute the actual scroll behavior.

## Programmatic Navigation Examples

All navigation flows through the same state machine, whether triggered by user interaction or imperative code:

```ts
// Push a new entry
router.navigate("/about");

// Replace current entry
router.navigate("/settings", { replace: true });

// Go back (POP)
router.navigate(-1);

```

Inspecting internal state directly:

```ts
router.subscribe(state => {
  console.log('Current location:', state.location.pathname);
  console.log('Matches:', state.matches.map(m => m.route.id));
});

```

## Key Implementation Files

| File | Role |
|------|------|
| [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts) | Core router implementation, state machine, `startNavigation`, `completeNavigation`, fetcher and blocker logic |
| [`packages/react-router/lib/router/history.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/history.ts) | History abstraction (`Action`, `Location`, `History` interface) and concrete implementations (`createBrowserHistory`, `createMemoryHistory`, `createHashHistory`) |
| [`packages/react-router/lib/router/utils.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/utils.ts) | Route matching via `matchRoutes`, route conversion utilities, data strategy helpers |
| [`packages/react-router/lib/router/links.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/links.ts) | URL construction utilities including `createHref` and `resolveTo` |

## Summary

- React Router stores all navigation state in a mutable `RouterState` object owned by a `createRouter` instance in [`packages/react-router/lib/router/router.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts)
- The state machine processes navigation through `startNavigation` and `completeNavigation`, ensuring atomic state updates
- History events from [`packages/react-router/lib/router/history.ts`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/history.ts) trigger the state machine, which coordinates route matching via `matchRoutes` in [`utils.ts`](https://github.com/remix-run/react-router/blob/main/utils.ts)
- Fetchers enable data loading without URL changes by managing entries in `state.fetchers` with independent lifecycle states
- Blockers intercept navigation through `shouldBlockNavigation` and the `blockerFunctions` map, storing `BlockerBlocked` entries until resolved
- Scroll restoration is managed through `savedScrollPositions` and evaluated during `completeNavigation` based on navigation type and flags

## Frequently Asked Questions

### What is the difference between `loaderData` and `actionData` in RouterState?

`loaderData` stores the results from route loaders executed during navigation or revalidation, providing data for rendering routes. `actionData` contains the return value from the most recent form submission or action invocation, typically used to display mutation results or optimistic UI updates immediately after a submission.

### How does React Router handle concurrent navigation requests?

The router maintains an `AbortController` that cancels in-flight navigation and data loading when a new navigation starts via `startNavigation`. This ensures that only the latest navigation completes its lifecycle, preventing race conditions and stale data updates when users rapidly click navigation links.

### Can navigation state persist across browser sessions?

While the `RouterState` itself is ephemeral and maintained in memory, React Router persists navigation keys and state through the History API's `location.state` and `location.key` properties. For full state persistence across sessions, implement custom serialization in the history configuration or use session storage with custom scroll restoration logic.

### Where does the actual DOM interaction happen if router.ts never touches the DOM?

The router emits state updates through `updateState` and `subscribe` callbacks that notify `<RouterProvider>`. React components then handle DOM operations such as scroll management through `useScrollRestoration` or navigation via `<Link>` components, keeping the router platform-agnostic and testable outside the browser environment.