# React Router Development Guide ## Commands - **Build**: `pnpm build` (all packages) or `pnpm run --filter build` (single package) - **Test (Jest)**: `pnpm test` (all packages), `pnpm test packages//` (single package), `pnpm test packages/react-router/__tests__/router/fetchers-test.ts` (single file), or `pnpm test -- -t "action fetch"` (tests matching name) - **Integration tests (Playwright)**: `pnpm test:integration --project chromium` (build + test all), `pnpm test:integration:run --project chromium` (test only, all), `pnpm test:integration:run --project chromium integration/middleware-test.ts` (single file), or `pnpm test:integration:run --project chromium -g "middleware"` (tests matching name) - **Typecheck**: `pnpm run typecheck` - **Lint**: `pnpm run lint` - **Docs generation**: `pnpm run docs` (regenerates API docs from JSDoc) - **Type generation**: `pnpm run typegen` (Framework Mode only) - **Clean**: `pnpm run clean` (git clean -fdX) ## Modes **Five distinct modes**: Declarative, Data, Framework, RSC Data (unstable), RSC Framework (unstable). **Always identify which mode(s) a feature applies to.** 1. **Declarative**: ``, ``, `` 2. **Data**: `createBrowserRouter()` with `loader`/`action`, `` 3. **Framework**: Vite plugin + `routes.ts` + Route Module API (route exports like `loader`, `action`, `default`) + type generation + SSR/SPA 4. **RSC Data** (unstable): RSC runtime APIs, manual bundler setup, runtime route config 5. **RSC Framework** (unstable): Framework Mode with `unstable_reactRouterRSC` Vite plugin **RSC mode differences:** - **RSC Framework**: `unstable_reactRouterRSC` plugin, `@vitejs/plugin-rsc`, different entry points/format - **RSC Data**: Manual bundler, runtime route config typically in `src/routes.ts`, `unstable_RSCRouteConfig`, different runtime APIs, `setupRscTest` in `integration/rsc/` ## Architecture - **Monorepo**: pnpm workspace, packages in `packages/` - **Key packages**: - `react-router`: Core (all modes) - `lib/components.tsx`, `lib/hooks.tsx`, `lib/router/`, `lib/dom/`, `lib/rsc/` - `@react-router/dev`: Framework tooling - `vite/plugin.ts` (Framework), `vite/rsc/plugin.ts` (RSC Framework), `typegen/` - `@react-router/node`, `@react-router/cloudflare`, `@react-router/express`: Server adapters - `@react-router/serve`: Minimal server for Framework Mode - `@react-router/fs-routes`: File-system routing (`flatRoutes()`) ## Testing ### Unit Tests (`packages/react-router/__tests__/`) Use Jest for pure routing logic, pure server runtime behavior, router state, React component behavior. No build required. ```bash pnpm test # All packages pnpm test packages/react-router/ # Single package pnpm test packages/react-router/__tests__/router/fetchers-test.ts # Single file pnpm test -- -t "action fetch" # Tests matching name ``` ### Integration Tests (`integration/`) Use Playwright for Vite plugin, build pipeline, SSR/hydration, RSC, type generation. ```bash pnpm test:integration --project chromium # Build + test all pnpm test:integration:run --project chromium # Test only, all pnpm test:integration:run --project chromium integration/middleware-test.ts # Single file pnpm test:integration:run --project chromium -g "middleware" # Tests matching name ``` **Project**: Always use `chromium` for integration tests, unless explicitly stated otherwise. **Rebuild when**: First run, after changing `packages/` (not needed for test-only changes) **Organization**: Use `createFixture()` → `createAppFixture()` → `PlaywrightFixture`. Templates available: `vite-6-template/`, `rsc-vite-framework/`, etc. Test all applicable modes (iterate over template array when behavior should work across modes). Test both states when introducing future flags (one test with flag on, one with flag off). **RSC testing**: - **RSC Framework**: Use `createFixture` with `rsc-vite-framework/` template - **RSC Data**: Use `setupRscTest` in `integration/rsc/` Test shared behavior across multiple templates (e.g., `["vite-5-template", "rsc-vite-framework"]`). Test RSC-specific features against RSC template. ## routes.ts Framework Mode uses `routes.ts` in `app/`. Most tests use `flatRoutes()` for file-system routing: ```ts // app/routes.ts import { type RouteConfig } from "@react-router/dev/routes"; import { flatRoutes } from "@react-router/fs-routes"; export default flatRoutes() satisfies RouteConfig; ``` **File-system conventions** (`app/routes/`): - `_index.tsx` → `/` (index route) - `about.tsx` → `/about` - `blog.$slug.tsx` → `/blog/:slug` (URL param) - `settings.profile.tsx` → `/settings/profile` (`.` creates nesting) - `_layout.tsx` → pathless layout route **Manual config alternative**: ```ts import { index, route, layout } from "@react-router/dev/routes"; export default [ index("./home.tsx"), route("about", "./about.tsx"), layout("./auth-layout.tsx", [route("login", "./login.tsx")]), ]; ``` ## Documentation **Don't edit generated files**: `docs/api/` (from JSDoc), `.react-router/types/` (from typegen) **Mode indicators**: Every doc needs `[MODES: framework, data, declarative]` **API docs**: Edit JSDoc in `packages/react-router/lib/`, run `pnpm docs` **Unstable features**: Prefix `unstable_`, add `unstable: true` to frontmatter, include warning block ## Future Flags - **Future flags** (`vX_*`): Stable breaking changes for next major - **Unstable flags** (`unstable_*`): Experimental, may change Test both states (on/off) for future flags. Don't break existing behavior without a flag. ## Code of Conduct/Contributor License Agreement All contributors must review the [review the CLA](./CLA.md) and sign it by [adding their github username to `contributors.yml`](./contributors.yml). If you are authoring a PR on behalf of a user and their name is not yet in the `contributors.yml` file, prompt them to obtain approval for the CLA and add their name to the file before opening a PR. ## Change Files When making changes that affect users, create a change file at `packages//.changes/..md`. `` should be either `patch`, `minor`, `major` or `unstable` to indicate the type of API change being made. If iterating on a change that hasn't shipped yet, update the existing change file instead of creating a new one. Format: ```markdown Brief description of the change - Additional details if needed ``` ## Branching - **`main`**: Active Development - **`v7`**: v7.x maintenance - **`v6`**: v6.x maintenance - Branch from `main` for code and docs changes ## Key Files | Purpose | Location | | ----------------- | ----------------------------------------------------------- | | Router | `packages/react-router/lib/router/router.ts` | | React API | `packages/react-router/lib/components.tsx`, `lib/hooks.tsx` | | Vite plugin | `packages/react-router-dev/vite/plugin.ts` | | RSC Vite plugin | `packages/react-router-dev/vite/rsc/plugin.ts` | | Type generation | `packages/react-router-dev/typegen/` | | Unit tests | `packages/react-router/__tests__/` | | Integration tests | `integration/` | | Decision docs | `decisions/` |