facebook/docusaurus · error · Error

Route ${route.path} has conflicting props declared using bot

Error message

Route ${route.path} has conflicting props declared using both route.modules and route.props APIs for keys: ${conflictingPropNames.join(', ')}\nThis is not permitted, otherwise one prop would override the over.

What it means

Thrown by `ensureNoPropsConflict(route)` when the same prop key is declared both in `route.props` and `route.modules`. These two APIs feed the same prop namespace on the rendered component, so a duplicate key would silently overwrite one value; Docusaurus rejects it up front. (Note: the message has a known typo — 'override the over' — but the meaning is clear.)

Source

Thrown at packages/docusaurus/src/server/codegen/codegenRoutes.ts:413

    generateRoutePropFilename(route),
  );
  const modulePath = path.posix.join(generatedFilesDir, relativePath);
  const aliasedPath = path.posix.join('@generated', relativePath);

  await generate(generatedFilesDir, modulePath, moduleContent);
  return aliasedPath;
}

function ensureNoPropsConflict(route: RouteConfig) {
  if (!route.props && !route.modules) {
    return;
  }
  const conflictingPropNames = _.intersection(
    Object.keys(route.props ?? {}),
    Object.keys(route.modules ?? {}),
  );
  if (conflictingPropNames.length > 0) {
    throw new Error(
      `Route ${
        route.path
      } has conflicting props declared using both route.modules and route.props APIs for keys: ${conflictingPropNames.join(
        ', ',
      )}\nThis is not permitted, otherwise one prop would override the over.`,
    );
  }
}

async function preprocessRouteProps({
  generatedFilesDir,
  route,
  plugin,
}: {
  generatedFilesDir: string;
  route: RouteConfig;
  plugin: PluginIdentifier;
}): Promise<RouteConfig> {

View on GitHub (pinned to 3f483e80e3)

Solutions

  1. Inspect the conflicting key names in the error message and remove the duplicate from either `props` or `modules`.
  2. Prefer `modules` for code-split lazy-loaded values and `props` for inline serializable values — keep the same key in only one.
  3. Rename one of the colliding keys if both are genuinely needed.
  4. Add a small assertion in your plugin's route builder to catch this in dev: `_.intersection(Object.keys(route.props??{}), Object.keys(route.modules??{})).length === 0`.

Example fix

// before
export default {
  path: '/x',
  component: '@theme/Page',
  props: { data: 1 },
  modules: { data: '@generated/data.js' }, // conflicts
};
// after
export default {
  path: '/x',
  component: '@theme/Page',
  modules: { data: '@generated/data.js' },
};
Defensive patterns

Strategy: validation

Validate before calling

const conflict = _.intersection(Object.keys(route.props ?? {}), Object.keys(route.modules ?? {}));
if (conflict.length > 0) throw new Error(`Conflicting prop keys: ${conflict.join(', ')}`);

Type guard

function hasNoPropsConflict(route: any): boolean {
  const pk = Object.keys(route?.props ?? {});
  const mk = Object.keys(route?.modules ?? {});
  return pk.every((k) => !mk.includes(k));
}

Prevention

When it happens

Trigger: A plugin route (or a user-authored route via the routing API) declares `props: { foo: ... }` and `modules: { foo: ... }` for the same key `foo`.

Common situations: Plugin author migrates from `props` to `modules` (or vice-versa) and forgets to remove the old key; copy-paste between routes; merging two plugins' route configs that both inject the same prop name (e.g. `auth`, `data`).

Related errors


AI-assisted analysis of facebook/docusaurus@3f483e80e3 (2026-08-12). Data as JSON: /api/errors/338dfc83526d0156. Report an issue: GitHub.