remix-run/react-router · error · Error
Unable to define routes with duplicate route id: "${route.id
Error message
Unable to define routes with duplicate route id: "${route.id}" What it means
Thrown by `defineRoutes()` from `@react-router/remix-routes-option-adapter` when two calls produce a route with the same `id`. The id is `options.id` if provided, otherwise derived from the `file` argument via `createRouteId(file)` (slashes normalized, file extension stripped). The manifest is keyed by id, so collisions make the structure ambiguous.
Source
Thrown at packages/react-router-remix-routes-option-adapter/defineRoutes.ts:103
// route(path, file, options, children)
// route(path, file, options)
options = optionsOrChildren || {};
}
let route: RouteManifestEntry = {
path: path ? path : undefined,
index: options.index ? true : undefined,
caseSensitive: options.caseSensitive ? true : undefined,
id: options.id || createRouteId(file),
parentId:
parentRoutes.length > 0
? parentRoutes[parentRoutes.length - 1].id
: "root",
file,
};
if (route.id in routes) {
throw new Error(
`Unable to define routes with duplicate route id: "${route.id}"`,
);
}
routes[route.id] = route;
if (children) {
parentRoutes.push(route);
children();
parentRoutes.pop();
}
};
callback(defineRoute);
alreadyReturned = true;
return routes;View on GitHub (pinned to 1fd704a7da)
Solutions
- Inspect the printed id and grep `defineRoute(` calls for any other call producing the same id.
- Give each colliding route a distinct `file`, or pass an explicit unique `options.id` for routes that intentionally share a file.
- If two routes legitimately need one module, define the module once and reference it from a single parent with child routes.
- Run `normalizeSlashes` mentally on each `file` argument to confirm they don't collapse to the same id.
Example fix
// before
defineRoutes((route) => {
route('foo', 'routes/foo.tsx');
route('foo/bar', 'routes/foo.tsx', () => {}); // duplicate id 'routes/foo'
});
// after
defineRoutes((route) => {
route('foo', 'routes/foo.tsx', () => {
route('foo/bar', 'routes/foo/bar.tsx');
});
}); Defensive patterns
Strategy: validation
Validate before calling
// Before calling defineRoutes, assert unique ids
import { normalizeSlashes } from '@react-router/remix-routes-option-adapter/normalizeSlashes';
function stripExt(file: string) { return file.replace(/\.[a-z0-9]+$/i, ''); }
function assertUniqueRouteIds(calls: Array<{ file: string; id?: string }>) {
const seen = new Set<string>();
for (const c of calls) {
const id = c.id ?? normalizeSlashes(stripExt(c.file));
if (seen.has(id)) throw new Error(`Duplicate route id: ${id}`);
seen.add(id);
}
} Prevention
- Keep a single source of truth for route files and let file paths drive ids.
- Never reuse an explicit `id` across routes.
- Add a unit test that runs your defineRoutes callback and asserts the manifest has N unique ids.
When it happens
Trigger: Two `defineRoute(path, file, ...)` calls with the same `file` string (e.g. `routes/parent` and `routes/parent.tsx` both collapse to id `routes/parent`), or two routes that pass the same explicit `options.id`. Also triggered by case-only or slash-only differences in `file` after normalization.
Common situations: Migrating from Remix's `defineRoutes` config and accidentally pointing two routes at one module; copy-pasting a route block and forgetting to change the file path; mixing manual `id` overrides that collide; Windows backslash paths colliding with POSIX forward-slash paths after `normalizeSlashes`.
Related errors
- Unable to define routes with duplicate route id: "${id}"
- Unable to prerender path because it does not match any route
- React Router presets must have a `name` property defined.
- Could not find package.json in ${rootDirectory} or any of it
- React Router Vite plugin not found in Vite config
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/b6f131d3fb9296f7.
Report an issue: GitHub.