react-router-auto-routes
Version:
Automatic folder-based routing with colocation for React Router v7+
259 lines (194 loc) β’ 11 kB
Markdown
# React Router Auto Routes
Automatic folder-based routing with colocation for React Router v7+.
## Principles
Built on [convention-over-configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) principlesβyour file structure defines your routes automatically, with smart defaults that just work, and scale well.
[Colocation](https://kentcdodds.com/blog/colocation) is a first-class feature:
> "Place code as close to where it's relevant as possible" β Kent C. Dodds
Keep your components, tests, utilities, and routes together. No more hunting across folders or artificial separation of concerns. The `+` prefix marks non-route files for cohesive, feature-based code organization.
## Features
- π **Flexible file organization** - Mix and match folder-based and dot-delimited notation
- π― **Prefix-based colocation** - Keep helpers and components alongside routes using `+` prefix
- π¦ **Monorepo / sub-apps support** - Mount routes from different folders to organize multi-app projects
- β‘ **ESM-only** - No CommonJS, built for modern tooling
- π§Ή **Clean API** - Simplified options and intuitive conventions
## Quick Start
Install:
```bash
npm install -D react-router-auto-routes
```
> The migration CLI relies on your project's own TypeScript install. Make sure `typescript@>=5.0` is already in `devDependencies` if you plan to run `npx migrate-auto-routes`.
Use in your app:
```ts
// app/routes.ts
import { autoRoutes } from 'react-router-auto-routes'
export default autoRoutes()
```
> **Migrating from remix-flat-routes?** See the [Migration Guide](#migration-guide) below.
## Routing Convention
**Folder-based structure:**
```
routes/
βββ index.tsx β / (index route)
βββ about.tsx β /about
βββ _auth/ β Pathless layout (no /auth in URL)
β βββ _layout.tsx β Auth layout
β βββ login.tsx β /login
β βββ signup.tsx β /signup
βββ blog/
β βββ _layout.tsx β Layout for /blog/* routes
β βββ index.tsx β /blog
β βββ $slug.tsx β /blog/:slug (dynamic param)
β βββ archive.tsx β /blog/archive
βββ dashboard/
β βββ _layout.tsx β Layout for dashboard routes
β βββ index.tsx β /dashboard
β βββ analytics.tsx β /dashboard/analytics
β βββ settings/
β βββ _layout.tsx β Layout for settings routes
β βββ index.tsx β /dashboard/settings
β βββ profile.tsx β /dashboard/settings/profile
βββ files/
βββ $.tsx β /files/* (splat - catch-all)
```
**Equivalent flat (dot-delimited) structure:**
```
routes/
βββ index.tsx β / (index route)
βββ about.tsx β /about
βββ _auth._layout.tsx β Auth layout
βββ _auth.login.tsx β /login
βββ _auth.signup.tsx β /signup
βββ blog._layout.tsx β Layout for /blog/* routes
βββ blog.index.tsx β /blog
βββ blog.$slug.tsx β /blog/:slug (dynamic param)
βββ blog.archive.tsx β /blog/archive
βββ dashboard._layout.tsx β Layout for dashboard routes
βββ dashboard.index.tsx β /dashboard
βββ dashboard.analytics.tsx β /dashboard/analytics
βββ dashboard.settings._layout.tsx β Layout for settings routes
βββ dashboard.settings.index.tsx β /dashboard/settings
βββ dashboard.settings.profile.tsx β /dashboard/settings/profile
βββ files.$.tsx β /files/* (splat - catch-all)
```
Both structures produce identical routes. Use folders for organization, flat files for simplicity, or mix both approaches as needed.
**Route patterns:**
- `index.tsx` or `_index.tsx` - Index routes (match parent folder's path).
- Index routes automatically nest under layouts with matching path segmentsβfor example, `admin/index.tsx` nests under `admin/_layout.tsx`.
- `_layout.tsx` - Layout with `<Outlet />` for child routes
- Other `_` prefixes (like `_auth/`) create pathless layout groups
- `$param` - Dynamic segments (e.g., `$slug` β `:slug`)
- `$.tsx` - Splat routes (catch-all)
- `(segment)` - Optional segments (e.g., `(en)` β `en?`)
- `($param)` - Optional dynamic params (e.g., `($lang)` β `:lang?`)
**Key insight:** Folders are just a convenience for organization. Without a parent file, `api/users.ts` behaves exactly like `api.users.ts` - both create the same `/api/users` route.
## Colocation with `+` Prefix
Keep helpers, components, and utilities alongside routes using the `+` prefix. Anything starting with `+` is ignored by the router.
```
routes/
βββ dashboard/
β βββ index.tsx β Route: /dashboard
β βββ +/
β β βββ helpers.ts
β β βββ types.tsx
β βββ +components/
β βββ data-table.tsx
βββ users/
βββ index.tsx β Route: /users
βββ +user-list.tsx
βββ $id/
βββ index.tsx β Route: /users/:id
βββ edit.tsx β Route: /users/:id/edit
βββ +/
βββ query.ts
βββ validation.ts
```
Import colocated files using relative paths:
```ts
import { formatDate } from './+/helpers'
```
**Rules:**
- **Allowed:** Use `+` prefixed files and folders anywhere inside route directories (including anonymous `+.tsx` files and `+/` folders)
- **Disallowed:** Don't place `+` entries at the routes root level like `routes/+helpers.ts` (but `routes/_top/+helpers.ts` is fine)
- **Note:** `+types` is [reserved](https://reactrouter.com/explanation/type-safety) for React Router's typegen virtual folders so avoid that name.
## Configuration Options
```ts
autoRoutes({
routesDir: 'routes',
ignoredRouteFiles: ['**/.*'], // Ignore dotfiles like .gitkeep
paramChar: '$',
colocationChar: '+',
routeRegex: /\.(ts|tsx|js|jsx|md|mdx)$/,
})
```
`.DS_Store` is always ignored automatically, even when you provide custom `ignoredRouteFiles`, and the migration CLI inherits the same default.
**Note:** Prefer using the `+` colocation prefix over `ignoredRouteFiles` when possible. Ignored files skip all processing including conflict detection, while colocated files still benefit from validation checks like ensuring proper placement. For example, place tests in `+test/` folders rather than using `**/*.test.{ts,tsx}` in `ignoredRouteFiles`.
### Monorepo / Sub-apps (Multiple Route Roots)
`routesDir` accepts two shapes:
- `string` β scan a single root. When omitted, the default `'routes'` resolves to `app/routes` so existing folder structures continue to work with zero config.
- `Record<string, string>` β mount filesystem folders to URL paths (key = URL path, value = filesystem folder). Folder paths resolve from the project root so you can mount packages that live outside `app/`.
Mount routes from different folders to organize sub-apps or monorepo packages:
```ts
autoRoutes({
routesDir: {
'/': 'app/routes',
'/api': 'api/routes',
'/docs': 'packages/docs/routes',
'/shop': 'packages/shop/routes',
},
})
```
**Example structure:**
```
app/
routes/
dashboard.tsx β /dashboard
settings/
_layout.tsx β /settings (layout)
index.tsx β /settings
api/
routes/
users/
index.tsx β /api/users
packages/
docs/
routes/
index.tsx β /docs
shop/
routes/
index.tsx β /shop
```
Routes from each mount stay isolated when resolving parents and dot-flattening, but still merged into a single manifest.
## Migration Guide
> **Note:** This migration tool is designed for projects using [remix-flat-routes](https://github.com/kiliman/remix-flat-routes) 0.8.\*
Ensure your project already lists `typescript@>=5.0`; the CLI resolves the compiler from your workspace.
Install the package, then run the migration CLI:
```bash
npx migrate-auto-routes
# or provide an explicit [source] [destination]
npx migrate-auto-routes app/routes app/new-routes
```
The CLI overwrites the target folder if it already exists. With no arguments it reads from `app/routes` and writes to `app/new-routes`. When you pass both arguments, the CLI uses the exact `sourceDir` and `targetDir` paths you provide.
**Built-in safety checks:** The CLI performs these automatically so you donβt have to.
- Verifies you are inside a Git repository and the route source folder (e.g. `app/routes`) has no pending changes before running the migration CLI
- Runs `npx react-router routes` before and after rewriting files
- Stages the migrated result in `app/new-routes` (or your custom target) before swapping it into place
- If successful, renames `app/routes` to `app/old-routes`, then moves the new routes into `app/routes`
- If the generated route output differs, prints a diff, restores the original folder, and keeps the migrated files at the target path for inspection
- When your project still imports `createRoutesFromFolders`/`remix-flat-routes`, the CLI updates `app/routes.ts` to export `autoRoutes()` so the snapshot check runs against the migrated tree
If everything looks good, you can uninstall the old packages:
```bash
npm uninstall remix-flat-routes
npm uninstall @react-router/remix-routes-option-adapter
```
### Deprecating `route.tsx`
Deprecating legacy `route.tsx` files in favor of `index.tsx` plus `+` colocation. Support remains for now, after which the matcher will be removed.
If you used `route.tsx` as the entry point for colocated helpers, follow these steps:
1. Move any colocated assets (loaders, helpers, tests) into a `+/` folder so they stay adjacent without being treated as routes.
2. Rename each `route.tsx` to `index.tsx` inside its directory so the folder name becomes the route segment.
3. Run `npx react-router routes` to confirm the manifest compiles cleanly and no lingering `route.tsx` entries remain. Double-check that colocated helpers stayed inside `+/` folders so they are not accidentally exposed as routes.
The migration CLI still recognizes `route.tsx` right now for backwards compatibility, but future releases will warn (and eventually drop support) once projects have had a full cycle to adopt the `index.tsx` pattern.
## Requirements
- Node.js >= 20
- React Router v7+
## Acknowledgments
This library is heavily inspired by [remix-flat-routes](https://github.com/kiliman/remix-flat-routes) by @kiliman. While this is a complete rewrite for React Router v7+, the core routing conventions and ideas stem from that excellent work.