# React Router Administrator & Developer Guide

This guide covers the complete architecture, configuration surface, deployment topology, security posture, and internal module reference for **React Router v7** — a multi-strategy router for React supporting SPA, SSR, and static prerendering modes. It targets administrators deploying React Router applications in production, developers extending the framework or building custom adapters, and architecture reviewers evaluating the project.

For end-user installation, routing patterns, and everyday usage, see the **User Guide**.

---

## 1. Architecture Overview

### 1.1 High-Level System Design

React Router is a monorepo containing 11 packages that together form a full-stack React framework. The architecture separates concerns into four distinct layers:

| Layer | Packages | Responsibility |
|-------|----------|----------------|
| **Core Runtime** | `react-router` | Route matching, data loading, navigation state, history management, React context distribution |
| **Build Tooling** | `@react-router/dev` | Vite plugin, CLI, config resolution, code generation, type generation, HMR |
| **Server Adapters** | `@react-router/express`, `@react-router/node`, `@react-router/cloudflare`, `@react-router/architect`, `@react-router/serve` | Platform-specific request/response translation, session storage |
| **Routing Conventions** | `@react-router/fs-routes`, `@react-router/remix-routes-option-adapter` | File-system routing, migration helpers |

Additional packages: `react-router-dom` (legacy re-export shim), `create-react-router` (project scaffolding CLI).

### 1.2 Request Lifecycle

A request through a React Router SSR application follows this path:

```
HTTP Request
  → Server Adapter (Express/Node/Cloudflare/Architect)
    → createRequestHandler({ build, getLoadContext })
      → matchRoutes(url)
        → Execute loaders (parallel by default)
          → Render React tree with RouterProvider
            → Serialize to HTML stream
              → Server Adapter writes HTTP Response
```

For client-side navigations after hydration:

```
Link click / navigate()
  → history.pushState
    → matchRoutes(url)
      → Fetch loaders via /__data endpoint (or single-fetch turbo-stream)
        → Re-render changed route segments
          → ScrollRestoration
```

### 1.3 Rendering Strategies

React Router supports three rendering strategies configured via `react-router.config.ts`:

| Strategy | Config | Build Output | Runtime |
|----------|--------|-------------|---------|
| **SSR** (default) | `ssr: true` | Client bundle + Server bundle | Server renders on every request |
| **SPA** | `ssr: false` | Client bundle + static HTML shell | Client-only rendering, no server |
| **Prerender** | `prerender: true \| string[] \| fn` | Client bundle + static HTML per path | Static files served by any HTTP server |

> [!NOTE]
> When `ssr: false`, React Router generates a static HTML shell with the client bundle. Loaders still run but execute entirely on the client. Server-side loaders, actions, and headers exports are not available in SPA mode.

### 1.4 Module System

All packages produce dual ESM/CJS builds via `tsup`. The build pipeline uses `__DEV__` defines to strip development-only code (warnings, validation) from production bundles. Each package's `package.json` uses conditional exports:

```json
{
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.mts",
        "default": "./dist/index.mjs"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  }
}
```

---

## 2. System Requirements

### 2.1 Runtime Requirements

| Requirement | Version | Notes |
|-------------|---------|-------|
| **Node.js** | >= 20.0.0 | Required for development and production server |
| **React** | >= 18.0.0 | React 19 supported with RSC features |
| **TypeScript** | >= 5.1 | Optional but strongly recommended; required for typegen |

### 2.2 Build Requirements

| Tool | Version | Notes |
|------|---------|-------|
| **pnpm** | 9.10.0 | Workspace protocol, catalog feature used |
| **Vite** | 5.x, 6.x, 7-beta, 8.x | Plugin supports multiple major versions |
| **tsup** | Latest | Package builds only (not application builds) |

### 2.3 Platform Support

| Platform | Adapter Package | Session Storage |
|----------|----------------|-----------------|
| **Node.js (Express)** | `@react-router/express` | Cookie, Memory, File |
| **Node.js (raw HTTP)** | `@react-router/node` | Cookie, Memory, File |
| **Node.js (built-in)** | `@react-router/serve` | Cookie, Memory, File |
| **Cloudflare Workers/Pages** | `@react-router/cloudflare` | Cookie, Memory, KV |
| **AWS Lambda (Architect)** | `@react-router/architect` | Cookie, Memory, DynamoDB |

> [!IMPORTANT]
> The `@react-router/serve` package is designed for production use. It includes Express with compression, morgan logging, and static file serving with appropriate cache headers. It is the default server when deploying with `react-router-serve`.

### 2.4 Environment Variables

| Variable | Default | Used By | Purpose |
|----------|---------|---------|---------|
| `NODE_ENV` | `"development"` | All packages | Controls `__DEV__` flag, logging verbosity, error detail |
| `PORT` | `3000` | `@react-router/serve`, framework servers | HTTP listen port |
| `HOST` | `"0.0.0.0"` | `@react-router/serve` | HTTP bind address |
| `REACT_ROUTER_ROOT` | `process.cwd()` | `@react-router/dev` config loader | Override project root directory |
| `ARC_SANDBOX` | None | `@react-router/architect` | Architect sandbox path for local development |

---

## 3. Configuration Reference

### 3.1 react-router.config.ts

The primary configuration file lives at the project root. It must provide a default export of type `Config`:

```typescript
import type { Config } from "@react-router/dev/config";

export default {
  // All options shown with defaults
} satisfies Config;
```

#### Complete Option Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `appDirectory` | `string` | `"app"` | Directory containing application source (routes, entry files, root.tsx) |
| `basename` | `string` | `"/"` | Base path for all routes. Set when deploying to a subdirectory |
| `buildDirectory` | `string` | `"build"` | Output directory for production builds |
| `serverBuildFile` | `string` | `"index.js"` | Filename for the server bundle entry point |
| `serverModuleFormat` | `"esm" \| "cjs"` | `"esm"` | Module format for server bundle output |
| `ssr` | `boolean` | `true` | Enable server-side rendering. `false` produces SPA mode |
| `prerender` | `boolean \| string[] \| Function \| Object` | `false` | Static prerendering configuration (see below) |
| `routeDiscovery` | `Object` | `{ mode: "lazy" }` | Controls how the route manifest is delivered to the client |
| `serverBundles` | `Function` | None | Split server code into named bundles based on route branches |
| `allowedActionOrigins` | `string[]` | `[]` | Additional origins allowed for form submissions (CSRF) |
| `future` | `Object` | `{}` | Feature flags for upcoming versions |
| `presets` | `Preset[]` | `[]` | Configuration presets that can override settings |
| `buildEnd` | `Function` | None | Hook called after build completes |

#### Prerender Configuration

The `prerender` option accepts several forms:

```typescript
// Prerender all static routes
prerender: true

// Prerender specific paths
prerender: ["/", "/about", "/pricing"]

// Dynamic path resolution
prerender: async ({ getStaticPaths }) => {
  const paths = await getStaticPaths();
  return ["/", ...paths];
}

// Object form with concurrency control
prerender: {
  paths: ["/", "/about"],
  unstable_concurrency: 4, // Parallel prerender workers
}
```

#### Route Discovery

```typescript
routeDiscovery: {
  mode: "lazy",          // "lazy" (default) or "initial"
  manifestPath: undefined // Custom path for the route manifest
}
```

In `lazy` mode, the route manifest is fetched on demand as the user navigates. In `initial` mode, the full manifest is included in the initial HTML payload — larger initial load but no manifest fetch waterfall.

#### Server Bundles

```typescript
serverBundles: ({ branch }) => {
  // branch is the array of matched routes
  const isAdmin = branch.some(r => r.id.startsWith("routes/admin"));
  return isAdmin ? "admin" : "main";
}
```

This splits the server build into separate bundles, useful for deploying different route trees to different infrastructure (e.g., admin routes to a separate Lambda).

#### Future Flags

```typescript
future: {
  v8_middleware: boolean,                          // Enable route middleware
  v8_splitRouteModules: boolean | "enforce",       // Tree-shake unused route exports
  v8_viteEnvironmentApi: boolean,                  // Use Vite's Environment API
  unstable_optimizeDeps: boolean,                  // Pre-bundle dependencies
  unstable_passThroughRequests: boolean,            // Pass unmatched requests through
  unstable_subResourceIntegrity: boolean,           // SRI hashes on script/link tags
  unstable_trailingSlashAwareDataRequests: boolean, // Match trailing slashes in data requests
  unstable_previewServerPrerendering: boolean,      // Preview prerendered output in dev
}
```

> [!WARNING]
> Flags prefixed with `v8_` will become the default behavior in React Router v8 and cannot be disabled after upgrade. Plan migration early. Flags prefixed with `unstable_` may change or be removed without notice.

### 3.2 Route Configuration via routes.ts

Routes are defined in `app/routes.ts` using helper functions:

```typescript
import {
  route,
  index,
  layout,
  prefix,
  relative,
  getAppDirectory,
} from "@react-router/dev/routes";
import type { RouteConfig } from "@react-router/dev/routes";

export default [
  index("routes/home.tsx"),
  route("about", "routes/about.tsx"),

  layout("routes/auth/layout.tsx", [
    route("login", "routes/auth/login.tsx"),
    route("register", "routes/auth/register.tsx"),
  ]),

  ...prefix("api", [
    route("users", "routes/api/users.tsx"),
    route("users/:id", "routes/api/users.$id.tsx"),
  ]),
] satisfies RouteConfig;
```

#### Route Helper API

| Helper | Signature | Purpose |
|--------|-----------|---------|
| `route` | `route(path, file, children?)` | Define a route with a URL path |
| `index` | `index(file)` | Define an index route (renders at parent path) |
| `layout` | `layout(file, children?)` | Define a pathless layout wrapper |
| `prefix` | `prefix(path, children)` | Add a path prefix to child routes without a layout |
| `relative` | `relative(dir, routes)` | Resolve file paths relative to a directory |
| `getAppDirectory` | `getAppDirectory()` | Returns the configured `appDirectory` path |

### 3.3 File-System Routing (@react-router/fs-routes)

When using the `@react-router/fs-routes` package, routes are derived from the filesystem:

```typescript
// app/routes.ts
import { flatRoutes } from "@react-router/fs-routes";

export default flatRoutes();
```

#### Naming Conventions

| Pattern | URL | Example File |
|---------|-----|-------------|
| `users.tsx` | `/users` | Layout for `/users/*` |
| `users._index.tsx` | `/users` (index) | Index route |
| `users.$id.tsx` | `/users/:id` | Dynamic parameter |
| `users.($id).tsx` | `/users/:id?` | Optional parameter |
| `files.$.tsx` | `/files/*` | Splat/catch-all |
| `_auth.login.tsx` | `/login` | Pathless layout child |
| `[sitemap.xml].tsx` | `/sitemap.xml` | Escaped literal dots |
| `users_.new.tsx` | `/users/new` | Opt out of parent layout |

Supported file extensions: `.js`, `.jsx`, `.ts`, `.tsx`, `.md`, `.mdx`.

> [!TIP]
> The `.` character in filenames translates to `/` in URL paths. Use `[brackets]` to include literal characters that would otherwise be interpreted as routing syntax. For example, `[sitemap.xml].tsx` creates a route at `/sitemap.xml`.

### 3.4 Vite Configuration

The React Router Vite plugin wraps around Vite's standard configuration:

```typescript
// vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [reactRouter()],
});
```

#### Cloudflare Configuration

```typescript
import { reactRouter } from "@react-router/dev/vite";
import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    cloudflareDevProxy(),
    reactRouter(),
  ],
});
```

Or with the official Cloudflare plugin:

```typescript
import { reactRouter } from "@react-router/dev/vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    cloudflare({ viteEnvironment: { name: "ssr" } }),
    reactRouter(),
  ],
});
```

### 3.5 Entry Files

Two optional entry files control the client and server rendering boundaries:

**app/entry.client.tsx** — Controls client-side hydration:

```typescript
import { HydratedRouter } from "react-router/dom";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <HydratedRouter />
    </StrictMode>
  );
});
```

**app/entry.server.tsx** — Controls server-side rendering:

```typescript
import { isbot } from "isbot";
import { renderToReadableStream } from "react-dom/server";
import { ServerRouter } from "react-router";
import type { EntryContext } from "react-router";

export default async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  routerContext: EntryContext,
) {
  const stream = await renderToReadableStream(
    <ServerRouter context={routerContext} url={request.url} />,
    {
      signal: request.signal,
      onError(error) {
        responseStatusCode = 500;
        console.error(error);
      },
    }
  );

  if (isbot(request.headers.get("user-agent"))) {
    await stream.allReady;
  }

  return new Response(stream, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}
```

Use `npx react-router reveal entry.client` or `npx react-router reveal entry.server` to generate default entry files for customization.

---

## 4. Deployment & Scaling

### 4.1 Build Output

Running `react-router build` produces:

```
build/
  client/
    assets/           # Hashed JS/CSS chunks (immutable, cache 1 year)
    favicon.ico       # Static assets from public/
  server/
    index.js          # Server entry point (or custom serverBuildFile)
```

> [!IMPORTANT]
> Files in `build/client/assets/` use content-hash filenames and should be served with `Cache-Control: public, max-age=31536000, immutable`. The `@react-router/serve` package handles this automatically. Custom server setups must configure this explicitly.

### 4.2 Deployment Targets

#### Node.js with @react-router/serve (Recommended)

The simplest production deployment:

```bash
npx react-router-serve build/server/index.js
```

This starts an Express server with:
- Compression middleware (gzip/brotli)
- Morgan access logging
- Static file serving with appropriate cache headers
- Source map support (when `NODE_ENV !== "production"`)

Environment variables: `PORT` (default 3000), `HOST` (default 0.0.0.0).

For RSC builds, `react-router-serve` detects the build type and uses the appropriate rendering pipeline.

#### Node.js with Express

```typescript
import express from "express";
import { createRequestHandler } from "@react-router/express";

const app = express();

// Serve static assets with long-term caching
app.use(
  "/assets",
  express.static("build/client/assets", {
    immutable: true,
    maxAge: "1y",
  })
);

// Serve other static files
app.use(express.static("build/client", { maxAge: "1h" }));

// Handle SSR requests
app.all(
  "*",
  createRequestHandler({
    build: await import("./build/server/index.js"),
    getLoadContext: (req, res) => ({
      // Custom context available in loaders/actions
    }),
  })
);

app.listen(3000);
```

#### Node.js Raw HTTP Server

```typescript
import { createServer } from "node:http";
import { createRequestListener } from "@react-router/node";

const listener = createRequestListener({
  build: await import("./build/server/index.js"),
});

createServer(listener).listen(3000);
```

#### Cloudflare Workers

```typescript
// server.ts
import { createRequestHandler } from "@react-router/cloudflare";

const handler = createRequestHandler(
  // @ts-expect-error virtual module
  () => import("virtual:react-router/server-build"),
  import.meta.env.MODE
);

export default {
  fetch(request, env, ctx) {
    return handler(request, {
      cloudflare: { env, ctx },
    });
  },
};
```

#### AWS Lambda via Architect

```typescript
// server.ts
import { createRequestHandler } from "@react-router/architect";

export const handler = createRequestHandler({
  build: await import("./build/server/index.js"),
});
```

### 4.3 Server Bundles for Multi-Region / Split Deploys

The `serverBundles` config option splits the server build by route branch, enabling deployment of different route trees to different infrastructure:

```typescript
// react-router.config.ts
export default {
  serverBundles: ({ branch }) => {
    if (branch.some(r => r.id.startsWith("routes/api"))) {
      return "api";     // Deploy to edge workers
    }
    return "ssr";       // Deploy to origin server
  },
} satisfies Config;
```

This produces separate bundles in `build/server/api/index.js` and `build/server/ssr/index.js`.

### 4.4 Static Prerendering

For sites that can be fully or partially prerendered:

```typescript
export default {
  prerender: {
    paths: async ({ getStaticPaths }) => {
      const posts = await fetchBlogPosts();
      return [
        "/",
        "/about",
        ...posts.map(p => `/blog/${p.slug}`),
      ];
    },
    unstable_concurrency: 4,
  },
} satisfies Config;
```

Prerendered output goes to `build/client/` as static HTML files. These can be served by any static file server (nginx, CDN, S3+CloudFront).

### 4.5 Docker Deployment

```dockerfile
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM node:20-alpine AS production
WORKDIR /app
COPY --from=build /app/build ./build
COPY --from=build /app/package.json ./
COPY --from=build /app/node_modules ./node_modules
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npx", "react-router-serve", "build/server/index.js"]
```

### 4.6 Scaling Considerations

React Router SSR applications are stateless by default (session state lives in cookies or external stores). Horizontal scaling requires:

1. **Session storage** — Use cookie-based sessions (no server affinity needed) or external stores (Redis, DynamoDB, Cloudflare KV)
2. **Static assets** — Serve from CDN. The content-hashed filenames in `build/client/assets/` enable aggressive caching
3. **Server bundles** — Use `serverBundles` to split compute-heavy routes to dedicated infrastructure
4. **Prerendering** — Prerender static pages to eliminate server compute entirely

---

## 5. Operations

### 5.1 CLI Reference

The `react-router` CLI provides five commands:

```bash
# Production build
react-router build [projectDir]
# Outputs: build/client/ and build/server/

# Development server with HMR
react-router dev [projectDir]
# Starts Vite dev server with SSR middleware

# Display route tree
react-router routes [projectDir]
# Add --json for JSON output

# Generate entry files for customization
react-router reveal [entry.client|entry.server]
# Creates app/entry.client.tsx or app/entry.server.tsx

# Generate route types
react-router typegen
# Add --watch for watch mode
# Outputs to .react-router/types/
```

### 5.2 Type Generation

React Router automatically generates TypeScript types for route modules. The typegen system produces types in `.react-router/types/` that provide type safety for:

- Loader and action function parameters
- `useLoaderData()` return types
- Route parameters (`useParams()`)
- Search parameters

Generated types are placed alongside route files in the type output directory:

```
.react-router/
  types/
    app/
      routes/
        +types.home.ts
        +types.users.$id.ts
```

Each generated file exports types specific to that route's parameters, loader data, and action data. These types are automatically imported when using the `Route.LoaderArgs`, `Route.ActionArgs`, and similar type helpers.

> [!NOTE]
> Run `react-router typegen --watch` during development to keep types in sync as routes change. The Vite plugin runs typegen automatically during `react-router dev`.

### 5.3 Build Output Verification

After building, verify the output:

```bash
# Check build output exists
ls -la build/client/assets/
ls -la build/server/

# Verify server bundle is valid
node -e "import('./build/server/index.js').then(m => console.log('OK', Object.keys(m)))"

# Check asset sizes
du -sh build/client/assets/*
```

### 5.4 Health Monitoring

For production SSR deployments, implement health checks:

```typescript
// app/routes/healthz.tsx
export function loader() {
  return new Response("OK", {
    status: 200,
    headers: { "Cache-Control": "no-store" },
  });
}
```

Key metrics to monitor:

| Metric | Source | Alert Threshold |
|--------|--------|-----------------|
| SSR response time | Server logs / APM | p99 > 500ms |
| Loader execution time | Custom instrumentation | Individual loader > 200ms |
| Client bundle size | Build output | Main chunk > 250KB gzipped |
| Hydration time | Client-side RUM | > 2s on 3G |
| Error boundary hits | Server logs | Any increase |

### 5.5 Logging

`@react-router/serve` uses Morgan for HTTP access logging. For custom servers, instrument at the adapter level:

```typescript
app.all("*", (req, res, next) => {
  const start = Date.now();
  res.on("finish", () => {
    console.log(JSON.stringify({
      method: req.method,
      path: req.path,
      status: res.statusCode,
      duration: Date.now() - start,
    }));
  });
  next();
}, createRequestHandler({ build }));
```

### 5.6 Error Handling

React Router uses a hierarchical error boundary system:

1. **Route-level `ErrorBoundary`** — Catches errors in that route's loader, action, or component
2. **Parent route `ErrorBoundary`** — Catches errors that bubble up from child routes
3. **Root `ErrorBoundary`** — Last resort, defined in `app/root.tsx`
4. **Server-level error handler** — `entry.server.tsx` `onError` callback for streaming errors

```typescript
// app/root.tsx
export function ErrorBoundary() {
  const error = useRouteError();
  if (isRouteErrorResponse(error)) {
    return <div>{error.status} {error.statusText}</div>;
  }
  return <div>Unexpected error</div>;
}
```

> [!CAUTION]
> Never expose raw error messages or stack traces to end users in production. The `ErrorBoundary` component receives the full error object, but your rendering logic should display a user-friendly message. Set `NODE_ENV=production` to suppress detailed error output from the framework.

---

## 6. Performance

### 6.1 Data Loading Optimization

React Router executes loaders in parallel for all matched routes. This is the primary performance lever:

```
GET /users/123/posts
  → root loader      ─┐
  → users loader      ├── All execute in parallel
  → users.$id loader  │
  → posts loader     ─┘
```

Optimize loaders by:

1. **Avoiding waterfall dependencies** — Each loader should fetch its own data independently
2. **Using `shouldRevalidate`** — Prevent unnecessary re-fetching on navigations that don't affect a route's data
3. **Deferring non-critical data** — Return promises from loaders to stream data after the initial shell

```typescript
export function shouldRevalidate({
  currentUrl,
  nextUrl,
  defaultShouldRevalidate,
}) {
  // Only revalidate if the search params changed
  if (currentUrl.searchParams.toString() === nextUrl.searchParams.toString()) {
    return false;
  }
  return defaultShouldRevalidate;
}
```

### 6.2 Code Splitting

React Router automatically code-splits by route. Each route module becomes a separate chunk loaded on demand. Additional strategies:

- **`v8_splitRouteModules`** — When enabled, tree-shakes unused exports from route modules. If a route only exports a `loader` and `default` component (no `action`, no `ErrorBoundary`), the unused exports are eliminated
- **`routeDiscovery.mode: "lazy"`** — The route manifest itself is loaded incrementally, reducing initial payload for large route trees
- **Dynamic imports within components** — Use `React.lazy()` for heavy sub-components within a route

### 6.3 Caching Strategy

| Asset Type | Path Pattern | Cache Header |
|-----------|-------------|-------------|
| Hashed assets | `/assets/*` | `public, max-age=31536000, immutable` |
| Static files | `/favicon.ico`, etc. | `public, max-age=3600` |
| HTML pages | `/*` | `no-cache` or short TTL |
| Data requests | `/__data` | Set via loader `headers` export |

Each route can export a `headers` function to control response caching:

```typescript
export function headers({ loaderHeaders }) {
  return {
    "Cache-Control": loaderHeaders.get("Cache-Control") ?? "public, max-age=300",
  };
}
```

### 6.4 Bundle Analysis

Vite's built-in `build.rollupOptions` can be used with analysis plugins:

```typescript
// vite.config.ts
import { visualizer } from "rollup-plugin-visualizer";

export default defineConfig({
  plugins: [reactRouter()],
  build: {
    rollupOptions: {
      plugins: [visualizer({ open: true })],
    },
  },
});
```

### 6.5 Streaming SSR

React Router streams HTML by default using `renderToReadableStream`. The initial shell renders immediately with `<Suspense>` boundaries, and deferred data streams in as it resolves. This provides fast Time to First Byte (TTFB) while still server-rendering the complete page.

For bot detection (ensuring crawlers receive complete HTML):

```typescript
// entry.server.tsx
if (isbot(request.headers.get("user-agent"))) {
  await stream.allReady; // Wait for all content before responding
}
```

---

## 7. Security

### 7.1 CSRF Protection

React Router includes built-in CSRF protection for form submissions (actions). The mechanism in `packages/react-router/lib/actions.ts`:

1. On every non-GET request, `throwIfPotentialCSRFAttack()` compares the `Origin` header against `Host` and `X-Forwarded-Host` headers
2. If the origin does not match, the request is rejected with a 403 response
3. Additional origins can be allowed via `allowedActionOrigins` in the config

```typescript
// react-router.config.ts
export default {
  allowedActionOrigins: [
    "https://admin.example.com",
    "*.example.com",        // Wildcard subdomain
    "**.example.com",       // Deep wildcard
  ],
} satisfies Config;
```

Wildcard matching uses micromatch glob patterns.

> [!WARNING]
> The CSRF check relies on the `Origin` header sent by browsers. API clients that do not send an `Origin` header (e.g., `curl`, server-to-server calls) will be blocked. For API routes that accept non-browser requests, handle CSRF differently (e.g., API tokens).

### 7.2 Session Security

#### Cookie Session Storage

```typescript
import { createCookieSessionStorage } from "react-router";

const { getSession, commitSession, destroySession } =
  createCookieSessionStorage({
    cookie: {
      name: "__session",
      httpOnly: true,      // Not accessible via JavaScript
      maxAge: 60 * 60 * 24, // 24 hours
      path: "/",
      sameSite: "lax",     // CSRF protection
      secrets: ["s3cr3t"], // Signing key(s) — first is active, rest for rotation
      secure: true,        // HTTPS only
    },
  });
```

Session security checklist:

| Setting | Required Value | Risk if Wrong |
|---------|---------------|---------------|
| `httpOnly` | `true` | XSS can steal session cookies |
| `secure` | `true` (production) | Session sent over HTTP, interceptable |
| `sameSite` | `"lax"` or `"strict"` | CSRF via cross-site form submissions |
| `secrets` | Non-empty array | Unsigned cookies, trivially forgeable |
| `maxAge` | Bounded value | Sessions that never expire |

> [!CAUTION]
> Cookie session secrets should be cryptographically random strings of at least 32 characters. Rotate secrets by prepending new secrets to the array — the first secret signs new cookies, while older secrets verify existing cookies during the rotation period.

#### Server-Side Session Storage

For sessions that should not be stored in cookies (large payloads, sensitive data):

| Storage | Package | Use Case |
|---------|---------|----------|
| `createFileSessionStorage()` | `@react-router/node` | Single-server deployments |
| Cloudflare KV | `@react-router/cloudflare` | Cloudflare Workers |
| DynamoDB | `@react-router/architect` | AWS deployments |
| `createMemorySessionStorage()` | `react-router` | Development/testing only |

### 7.3 Content Security

React Router does not inject inline scripts by default in SSR mode. The hydration data is serialized as a `<script>` tag with a deterministic structure. For strict CSP:

```typescript
// entry.server.tsx — add nonce to scripts
export default function handleRequest(
  request: Request,
  status: number,
  headers: Headers,
  context: EntryContext,
) {
  const nonce = crypto.randomUUID();
  headers.set(
    "Content-Security-Policy",
    `script-src 'nonce-${nonce}' 'strict-dynamic';`
  );
  // Pass nonce to renderToReadableStream options
}
```

The `unstable_subResourceIntegrity` future flag adds SRI hashes to `<script>` and `<link>` tags for additional integrity verification.

### 7.4 Module Boundary Enforcement

React Router enforces server/client module boundaries:

- Files/directories named `.server` (e.g., `db.server.ts`, `.server/`) are excluded from client bundles
- Files/directories named `.client` are excluded from server bundles
- Attempting to import a `.server` module from client code produces a build error

> [!TIP]
> Use the `.server` suffix convention for any module containing secrets, database connections, or server-only dependencies. This prevents accidental bundling of sensitive code into the client JavaScript.

### 7.5 Headers Export

Each route module can export a `headers` function to set security headers:

```typescript
export function headers() {
  return {
    "X-Frame-Options": "DENY",
    "X-Content-Type-Options": "nosniff",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Permissions-Policy": "camera=(), microphone=(), geolocation=()",
  };
}
```

For headers that apply to all routes, set them at the server adapter level (Express middleware, Cloudflare Workers headers, etc.).

---

## 8. Developer Internals

### 8.1 Core Router Engine

The router core lives in `packages/react-router/lib/router/` and implements:

**History Management** — Wraps the browser History API (or in-memory history for SSR/tests) with:
- `pushState` / `replaceState` abstraction
- Blocker support (preventing navigation with unsaved changes)
- Pop state event handling

**Route Matching** — Implements a ranked route matching algorithm:
1. Routes are scored by specificity (static segments > dynamic > splat)
2. Matching considers nested route trees with relative paths
3. `PrefixLookupTrie` (in `@react-router/fs-routes`) provides O(prefix-length) lookup for file-system routes

**Data Loading** — The data pipeline:
1. Match routes for the target URL
2. Determine which loaders need to run (respecting `shouldRevalidate`)
3. Execute loaders in parallel via `Promise.all`
4. For actions: execute the action first, then revalidate affected loaders
5. Handle redirects at any point in the pipeline
6. Commit navigation state and trigger React re-render

### 8.2 React Context Architecture

React Router distributes state through several React contexts defined in `packages/react-router/lib/context.tsx`:

| Context | Purpose | Consumer Hooks |
|---------|---------|---------------|
| `DataRouterContext` | Router instance, navigator | `useNavigate`, `useSubmit` |
| `DataRouterStateContext` | Current router state (location, matches, errors) | `useLocation`, `useMatches` |
| `RouteContext` | Current route match, outlet | `useOutlet`, `useParams` |
| `RouteErrorContext` | Error for current route | `useRouteError` |
| `NavigationContext` | Navigation helpers | `useNavigate`, `useLinkClickHandler` |

### 8.3 Component Hierarchy

```
<RouterProvider router={router}>
  <DataRouterContext.Provider>
    <DataRouterStateContext.Provider>
      <Router>
        <NavigationContext.Provider>
          <LocationContext.Provider>
            <Routes>
              <Route element={<RootLayout />}>
                <Outlet />  ← Renders matched child route
              </Route>
            </Routes>
          </LocationContext.Provider>
        </NavigationContext.Provider>
      </Router>
    </DataRouterStateContext.Provider>
  </DataRouterContext.Provider>
</RouterProvider>
```

### 8.4 Key Internal Classes

| Class | Location | Purpose |
|-------|----------|---------|
| `PrefixLookupTrie` | `packages/react-router-fs-routes` | Trie-based route matching for file-system routes |
| `StreamPump` | `packages/react-router-node` | Converts Web `ReadableStream` to Node.js streams |
| `Deferred` | `packages/react-router/lib/components.tsx` | Internal promise wrapper for `<Await>` component |
| `AwaitErrorBoundary` | `packages/react-router/lib/components.tsx` | Error boundary wrapping deferred data resolution |
| `RenderErrorBoundary` | `packages/react-router/lib/components.tsx` | Error boundary for route component rendering |
| `PlaywrightFixture` | Integration tests | Helper for Playwright-based integration tests |
| `CopyTemplateError` | `packages/create-react-router` | Error during project scaffolding |

### 8.5 Vite Plugin Internals

The Vite plugin (`packages/react-router-dev/vite/`) orchestrates the build and dev experience:

**Config Resolution:**
1. Load `react-router.config.ts` via Vite's module resolution
2. Apply presets (presets can modify any config option)
3. Resolve route tree from `routes.ts` or file-system convention
4. Generate virtual modules for server build entry

**Development Server:**
1. Vite dev server starts with SSR middleware
2. Routes are watched for changes → automatic typegen
3. HMR handles component updates without full page reload
4. Server modules are re-evaluated on change (no server restart needed)

**Production Build:**
1. Client build: Vite builds the client entry with route-based code splitting
2. Server build: Vite builds the server entry with route handlers inlined
3. Manifest generation: Asset manifest for SSR → client asset resolution
4. Type generation: Final pass of route types

**Virtual Modules:**
- `virtual:react-router/server-build` — Resolved server build for adapters
- `virtual:react-router/browser-manifest` — Client route manifest

### 8.6 RSC Support (Unstable)

React Server Components support lives in `packages/react-router/lib/rsc/` and is considered unstable. It provides:

- Server-side component rendering without client JavaScript
- Integration with React's streaming SSR pipeline
- `@react-router/serve` detects RSC builds and uses the appropriate server entry

### 8.7 Module Analysis by Package

#### react-router (Core)

| Module Path | Purpose |
|------------|---------|
| `lib/router/router.ts` | Core router state machine, navigation, data loading |
| `lib/router/history.ts` | History abstraction (browser, hash, memory) |
| `lib/router/utils.ts` | Route matching, path parsing, ranking |
| `lib/components.tsx` | RouterProvider, Route, Routes, Outlet, Navigate, Await |
| `lib/hooks.tsx` | useNavigate, useLocation, useParams, useLoaderData, useActionData, useRouteError, etc. |
| `lib/dom/global.ts` | BrowserRouter, HashRouter setup |
| `lib/dom/lib.tsx` | Link, NavLink, Form, ScrollRestoration, useFetcher, useSearchParams |
| `lib/dom/ssr/components.tsx` | HydratedRouter, ServerRouter, Meta, Links, Scripts |
| `lib/dom/ssr/entry.ts` | EntryContext for SSR |
| `lib/dom/ssr/routes.tsx` | Route module loading and caching |
| `lib/dom/ssr/single-fetch.tsx` | Single-fetch data endpoint (turbo-stream) |
| `lib/server-runtime/server.ts` | createRequestHandler for server adapters |
| `lib/server-runtime/sessions.ts` | Session API, createSessionStorage |
| `lib/server-runtime/cookies.ts` | Cookie parsing, signing, serialization |
| `lib/server-runtime/data.ts` | Loader/action data serialization |
| `lib/context.tsx` | All React context definitions |
| `lib/actions.ts` | CSRF protection, action handling |
| `lib/rsc/` | React Server Components (unstable) |

#### @react-router/dev (Build Tooling)

| Module Path | Purpose |
|------------|---------|
| `vite/plugin.ts` | Main Vite plugin entry point |
| `vite/dev.ts` | Development server middleware |
| `vite/build.ts` | Production build orchestration |
| `vite/styles.ts` | CSS handling and HMR |
| `config/config.ts` | Configuration loading and validation |
| `config/routes.ts` | Route tree resolution |
| `typegen/index.ts` | Type generation orchestrator |
| `typegen/generate.ts` | Route type file generation |
| `cli/run.ts` | CLI command dispatch |

#### @react-router/serve (Production Server)

| Module Path | Purpose |
|------------|---------|
| `src/index.ts` | Express server setup: compression, morgan, static serving, request handler |
| `bin/react-router-serve.ts` | CLI entry point, port/host resolution |

#### @react-router/node

| Module Path | Purpose |
|------------|---------|
| `sessions/fileStorage.ts` | File-based session storage with atomic writes |
| `stream.ts` | Web stream ↔ Node.js stream conversion via `StreamPump` |
| `index.ts` | createRequestListener for raw HTTP servers |

#### @react-router/cloudflare

| Module Path | Purpose |
|------------|---------|
| `worker.ts` | Cloudflare Workers request handler adapter |
| `sessions/kvStorage.ts` | Cloudflare KV session storage |

#### @react-router/architect

| Module Path | Purpose |
|------------|---------|
| `server.ts` | AWS Lambda event → Web Request conversion |
| `sessions/dynamodbStorage.ts` | DynamoDB session storage |

#### @react-router/fs-routes

| Module Path | Purpose |
|------------|---------|
| `flatRoutes.ts` | File-system to route tree conversion |
| `routeManifest.ts` | Route manifest generation from filesystem |

### 8.8 Testing Infrastructure

**Unit Tests (Jest):**
- Each package has `jest.config.js` extending shared config from `jest/jest.config.shared`
- Tests co-located with source or in `__tests__/` directories
- `createRoutesStub()` utility for testing route modules in isolation

**Integration Tests (Playwright):**
- Located in `integration/` directory
- `playwright.config.ts` at integration root
- `PlaywrightFixture` class provides helpers for:
  - Starting dev/production servers
  - Navigation and interaction
  - Network request interception
  - Console log capture

```typescript
// Testing a route module
import { createRoutesStub } from "react-router";
import { render, screen } from "@testing-library/react";

const Stub = createRoutesStub([
  {
    path: "/",
    Component: MyComponent,
    loader: () => ({ message: "hello" }),
  },
]);

render(<Stub initialEntries={["/"]} />);
```

---

## 9. Integration Points

### 9.1 create-react-router (Scaffolding)

The `create-react-router` package provides project scaffolding:

```bash
npx create-react-router@latest my-app
# Or with a specific template
npx create-react-router@latest my-app --template remix-run/react-router/templates/basic
```

Templates are fetched from GitHub repositories. The scaffolding process:
1. Clone or download the template
2. Replace template placeholders
3. Initialize git repository
4. Display setup instructions

`CopyTemplateError` is thrown when template files cannot be written (permissions, disk space).

### 9.2 MDX Integration

React Router supports MDX files as route modules when configured with an MDX Vite plugin:

```typescript
// vite.config.ts
import mdx from "@mdx-js/rollup";
import { reactRouter } from "@react-router/dev/vite";

export default defineConfig({
  plugins: [mdx(), reactRouter()],
});
```

MDX files (`.md`, `.mdx`) in the routes directory are treated as route modules. They can export `loader`, `action`, `headers`, `meta`, and `handle` alongside their content.

### 9.3 Presets System

Presets allow third-party packages to provide React Router configuration:

```typescript
// A preset package
import type { Preset } from "@react-router/dev/config";

export function myPreset(): Preset {
  return {
    name: "my-preset",
    reactRouterConfig: () => ({
      serverModuleFormat: "cjs",
      // Any config option can be set
    }),
    reactRouterVitePlugin: () => ({
      // Vite plugin options
    }),
  };
}

// Usage in react-router.config.ts
import { myPreset } from "my-preset";

export default {
  presets: [myPreset()],
} satisfies Config;
```

Presets are applied in order; later presets override earlier ones. Direct config options override all presets.

### 9.4 getLoadContext

Every server adapter supports `getLoadContext` to inject server-specific context into loaders and actions:

```typescript
// Express
createRequestHandler({
  build,
  getLoadContext: (req, res) => ({
    db: req.app.locals.db,
    user: req.user,
  }),
});

// Cloudflare Workers
handler(request, {
  cloudflare: { env, ctx },
});
```

The context is available as the second argument to loaders and actions:

```typescript
export function loader({ request, context }) {
  const db = context.db;
  // ...
}
```

### 9.5 @react-router/remix-routes-option-adapter

For projects migrating from Remix, this adapter translates Remix's `routes` option format to React Router's `routes.ts` format:

```typescript
// app/routes.ts
import { remixRoutesOptionAdapter } from "@react-router/remix-routes-option-adapter";

export default remixRoutesOptionAdapter((defineRoutes) => {
  return defineRoutes((route) => {
    route("/", "routes/home.tsx", { index: true });
    route("/about", "routes/about.tsx");
  });
});
```

### 9.6 Known Issues and Workarounds

| Issue | Description | Workaround |
|-------|-------------|------------|
| #15007 | Concurrent Vite config resolution leaks route state when using `flatRoutes()` | Avoid parallel Vite config resolution in custom tooling |
| #14754 | Duplicated styles with suspended components since v7.9.5 | Pin to v7.9.4 or monitor for patch |
| #14997 | Server-only module error with `.server` directory naming | Use `.server.ts` file suffix instead of `.server/` directory |
| #14991 | pnpm requires `trustPolicyExclude` for `@react-router/dev` | Add `@react-router/dev` to pnpm's `trustPolicyExclude` |
| #14744 | Typegen bug with `*` in folder names | Avoid `*` in directory names |
| #14903 | Middleware not handling redirects with `useFetcher` | Handle redirects in the fetcher action directly |

> [!NOTE]
> Check the React Router GitHub repository for the latest status on these issues. Many may be resolved in newer releases beyond the source commit analyzed here.

### 9.7 Build End Hook

The `buildEnd` hook runs after the production build completes:

```typescript
// react-router.config.ts
export default {
  buildEnd: async ({ buildManifest, reactRouterConfig, viteConfig }) => {
    // Generate sitemap from build manifest
    // Upload assets to CDN
    // Notify deployment pipeline
  },
} satisfies Config;
```

### 9.8 Middleware (v8 Future Flag)

When `future.v8_middleware` is enabled, route modules can export middleware:

```typescript
export const unstable_middleware = [
  async function authMiddleware({ request, context }, next) {
    const session = await getSession(request);
    context.user = session.get("user");
    return next();
  },
];
```

Middleware runs before loaders/actions and can modify context, short-circuit with responses, or pass through to the next handler.

### 9.9 Quick Reference Tables

#### Exported Functions from Route Modules

| Export | Purpose | Server/Client |
|--------|---------|---------------|
| `default` | Route component | Client (+ SSR) |
| `loader` | Data loading (GET) | Server (or client in SPA) |
| `clientLoader` | Client-side data loading | Client only |
| `action` | Mutation handling (POST/PUT/DELETE) | Server (or client in SPA) |
| `clientAction` | Client-side mutation handling | Client only |
| `ErrorBoundary` | Error UI for this route | Client (+ SSR) |
| `headers` | HTTP response headers | Server |
| `handle` | Arbitrary data exposed via `useMatches` | Both |
| `links` | `<link>` tags for this route | Both |
| `meta` | `<meta>` and `<title>` tags | Both |
| `shouldRevalidate` | Control loader re-execution | Client |
| `HydrateFallback` | UI shown during client hydration | Client |
| `unstable_middleware` | Request middleware (v8 flag) | Server |

#### Core Hooks Reference

| Hook | Returns | Purpose |
|------|---------|---------|
| `useLoaderData()` | Loader return value | Access route loader data |
| `useActionData()` | Action return value | Access route action data |
| `useParams()` | `Params` | URL parameters |
| `useSearchParams()` | `[URLSearchParams, setter]` | Query string parameters |
| `useLocation()` | `Location` | Current location object |
| `useNavigate()` | `NavigateFunction` | Programmatic navigation |
| `useNavigation()` | `Navigation` | Pending navigation state |
| `useRouteError()` | `unknown` | Error caught by error boundary |
| `useMatches()` | `UIMatch[]` | All matched routes with handle data |
| `useFetcher()` | `Fetcher` | Non-navigation data loading/mutations |
| `useFetchers()` | `Fetcher[]` | All active fetchers |
| `useRouteLoaderData(routeId)` | Loader data | Access another route's loader data |
| `useRevalidator()` | `{ revalidate, state }` | Manually trigger revalidation |
| `useBlocker()` | `Blocker` | Prevent navigation (unsaved changes) |

---

_Generated May 2026 from remix-run/react-router at b80380d. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
