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

  1. Inspect the printed id and grep `defineRoute(` calls for any other call producing the same id.
  2. Give each colliding route a distinct `file`, or pass an explicit unique `options.id` for routes that intentionally share a file.
  3. If two routes legitimately need one module, define the module once and reference it from a single parent with child routes.
  4. 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

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


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/b6f131d3fb9296f7. Report an issue: GitHub.