{"record":{"id":"21830fb56205a6fe","repo":"remix-run/react-router","slug":"invalid-route-module-exports-the-following-pairs","errorCode":null,"errorMessage":"Invalid route module exports. The following pairs of exports are mutually exclusive and cannot be exported from the same module:\n- ${clientExport} and ${serverExport}","messagePattern":"Invalid route module exports\\. The following pairs of exports are mutually exclusive and cannot be exported from the same module:\n- (.+?) and (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/react-router-dev/vite/rsc/virtual-route-modules.ts","lineNumber":478,"sourceCode":"const MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS = new Map([\n  [\"ErrorBoundary\", \"ServerErrorBoundary\"],\n  [\"HydrateFallback\", \"ServerHydrateFallback\"],\n  [\"Layout\", \"ServerLayout\"],\n  [\"default\", \"ServerComponent\"],\n]);\n\nfunction validateRouteModuleExports(toValidate: string[]) {\n  let errors: [string, string][] = [];\n  for (let [clientExport, serverExport] of MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS) {\n    if (\n      toValidate.includes(clientExport) &&\n      toValidate.includes(serverExport)\n    ) {\n      errors.push([clientExport, serverExport]);\n    }\n  }\n  if (errors.length > 0) {\n    throw new Error(\n      `Invalid route module exports. The following pairs of exports are mutually exclusive and cannot be exported from the same module:\\n` +\n        errors\n          .map(\n            ([clientExport, serverExport]) =>\n              `- ${clientExport} and ${serverExport}`,\n          )\n          .join(\"\\n\"),\n    );\n  }\n}\n\ntype RouteChunks = ReturnType<typeof _detectRouteChunks>;\n\nfunction detectRouteChunks(\n  cache: Cache,\n  id: string,\n  code: string,\n  isRootRouteModule: boolean,","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/remix-run/react-router/blob/6beaca39526d5716c3c112ebb0782765baa5a9ce/packages/react-router-dev/vite/rsc/virtual-route-modules.ts#L460-L496","documentation":"When the RSC Vite plugin materializes a route module, validateRouteModuleExports checks the module's export list against MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS — the pairs default/ServerComponent, Layout/ServerLayout, ErrorBoundary/ServerErrorBoundary, and HydrateFallback/ServerHydrateFallback. If any pair is co-exported, the build fails listing every offending pair. This is the static, build-time counterpart of the runtime wrapper checks, so the error appears during 'react-router build'/'react-router dev' instead of at render time.","triggerScenarios":"A route module exporting both members of any pair, e.g. both 'default' and 'ServerComponent', or both 'HydrateFallback' and 'ServerHydrateFallback'; multiple violations are all listed in one error.","commonSituations":"Adopting RSC on an existing Framework-mode app: the old client exports are still present while server variants get added; generated or scaffolded routes that bundle both variants; merging example code during migration.","solutions":["For each pair listed in the message, delete one of the two exports (keep the Server variant only when the logic must run server-side)","Re-run the build to confirm no other pairs remain — the error lists all violations at once","Move shared code used by both variants into a separate module and import it"],"exampleFix":"// before — app/routes/post.tsx\nexport default function Route() { /* client component */ }\nexport function ServerComponent() { /* server component */ }\n\n// after\nexport function ServerComponent() { /* single server component */ }","handlingStrategy":"validation","validationCode":"// mirror of the library's MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS map\nconst PAIRS: [string, string][] = [\n  [\"ErrorBoundary\", \"ServerErrorBoundary\"],\n  [\"HydrateFallback\", \"ServerHydrateFallback\"],\n  [\"Layout\", \"ServerLayout\"],\n  [\"default\", \"ServerComponent\"],\n];\nfor (const file of routeFiles) {\n  const mod: Record<string, unknown> = await import(pathToFileURL(file).href);\n  for (const [client, server] of PAIRS) {\n    if (mod[client] && mod[server]) {\n      throw new Error(`${file}: ${client} and ${server} are mutually exclusive`);\n    }\n  }\n}","typeGuard":"function findExclusiveExportPairs(exportNames: string[]): [string, string][] {\n  const pairs: [string, string][] = [\n    [\"ErrorBoundary\", \"ServerErrorBoundary\"],\n    [\"HydrateFallback\", \"ServerHydrateFallback\"],\n    [\"Layout\", \"ServerLayout\"],\n    [\"default\", \"ServerComponent\"],\n  ];\n  return pairs.filter(([c, s]) => exportNames.includes(c) && exportNames.includes(s));\n}","tryCatchPattern":null,"preventionTips":["Treat client/server export variants as either-or per route module","Run 'react-router build' in CI — the transform-time validator lists all violations","Add an ESLint rule or script banning co-occurrence of the four pairs"],"tags":["rsc","route-module","exports","build-validation","naming"],"backgroundTag":"mutually-exclusive-exports","analyzedSha":"6beaca39526d5716c3c112ebb0782765baa5a9ce","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}