{"record":{"id":"164b58e9b147ba60","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${errors.map(([clientExport, serverExport]) => `- ${clientExport} and ${serverExport}`).join(\"\\n\")}","messagePattern":"Invalid route module exports\\. The following pairs of exports are mutually exclusive and cannot be exported from the same module:\n(.+?) and (.+?)`\\)\\.join\\(\"\\\\n\"\\)\\}","errorType":"exception","errorClass":"Error","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/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router-dev/vite/rsc/virtual-route-modules.ts#L460-L496","documentation":"Build-time validation in virtual-route-modules.ts: the MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS map lists four client/server pairs — (default, ServerComponent), (Layout, ServerLayout), (ErrorBoundary, ServerErrorBoundary), (HydrateFallback, ServerHydrateFallback). validateRouteModuleExports collects every pair where BOTH members appear in the module's export list and throws a single aggregated error listing them. This is the compile-time counterpart of the runtime guards (errors 47–50).","triggerScenarios":"During RSC Framework Mode build/vite-dev, a route module is analyzed and its static export names are passed to validateRouteModuleExports. If any mutually exclusive pair co-occurs, the error lists all offending pairs.","commonSituations":"Same as 47–50 but caught at build time: refactoring route modules without removing the superseded export. Bulk migration from Framework Mode to RSC Framework Mode leaving duplicates across many files.","solutions":["Read the error body — each line names the conflicting pair (e.g. `- default and ServerComponent`).","For each listed pair, remove one of the two exports from that module (see fixes for errors 47–50).","Re-run the build; the validator re-scans all route modules and reports any remaining pairs.","Add an ESLint rule or pre-commit grep to prevent re-introducing both exports."],"exampleFix":"// build error lists: `- default and ServerComponent`, `- Layout and ServerLayout`\n// fix: in each flagged file, delete the client export when a server export exists\n// app/routes/foo.tsx — before\nexport default function Foo() { /* client */ }\nexport function ServerComponent() { /* server */ }\nexport function Layout({ children }) { /* client */ }\nexport function ServerLayout({ children }) { /* server */ }\n// after\nexport function ServerComponent() { /* server */ }\nexport function ServerLayout({ children }) { /* server */ }","handlingStrategy":"validation","validationCode":"const MUTUALLY_EXCLUSIVE = [['default','ServerComponent'],['Layout','ServerLayout'],['ErrorBoundary','ServerErrorBoundary'],['HydrateFallback','ServerHydrateFallback']];\nfunction detectConflicts(exports: string[]): [string,string][] {\n  return MUTUALLY_EXCLUSIVE.filter(([a,b]) => exports.includes(a) && exports.includes(b));\n}\n// run detectConflicts over each route module's static export list before build","typeGuard":"function routeModuleIsValid(exports: string[]): boolean {\n  const pairs = [['default','ServerComponent'],['Layout','ServerLayout'],['ErrorBoundary','ServerErrorBoundary'],['HydrateFallback','ServerHydrateFallback']];\n  return pairs.every(([a,b]) => !(exports.includes(a) && exports.includes(b)));\n}","tryCatchPattern":null,"preventionTips":["Add a CI step that scans app/routes/** for mutually exclusive export pairs.","Adopt an ESLint custom rule mirroring MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS.","Run typegen in CI; it surfaces the same conflicts at compile time."],"tags":["rsc","route-module","exports","build-time","validation"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}