remix-run/react-router · error
Invalid route module exports. The following pairs of…
Error message
Invalid route module exports. The following pairs of exports are mutually exclusive and cannot be exported from the same module:
- ${clientExport} and ${serverExport} What it means
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.
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
Example fix
// before — app/routes/post.tsx
export default function Route() { /* client component */ }
export function ServerComponent() { /* server component */ }
// after
export function ServerComponent() { /* single server component */ } Defensive patterns
Strategy: validation
Validate before calling
// mirror of the library's MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS map
const PAIRS: [string, string][] = [
["ErrorBoundary", "ServerErrorBoundary"],
["HydrateFallback", "ServerHydrateFallback"],
["Layout", "ServerLayout"],
["default", "ServerComponent"],
];
for (const file of routeFiles) {
const mod: Record<string, unknown> = await import(pathToFileURL(file).href);
for (const [client, server] of PAIRS) {
if (mod[client] && mod[server]) {
throw new Error(`${file}: ${client} and ${server} are mutually exclusive`);
}
}
} Type guard
function findExclusiveExportPairs(exportNames: string[]): [string, string][] {
const pairs: [string, string][] = [
["ErrorBoundary", "ServerErrorBoundary"],
["HydrateFallback", "ServerHydrateFallback"],
["Layout", "ServerLayout"],
["default", "ServerComponent"],
];
return pairs.filter(([c, s]) => exportNames.includes(c) && exportNames.includes(s));
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Module cannot have both a default export and a…
- Module cannot have both a HydrateFallback export and a…
- Module cannot have both a Layout export and a ServerLayout…
- Module cannot have both an ErrorBoundary export and a…
- Error splitting route module
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/21830fb56205a6fe.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router-dev/vite/rsc/virtual-route-modules.ts:478
const MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS = new Map([
["ErrorBoundary", "ServerErrorBoundary"],
["HydrateFallback", "ServerHydrateFallback"],
["Layout", "ServerLayout"],
["default", "ServerComponent"],
]);
function validateRouteModuleExports(toValidate: string[]) {
let errors: [string, string][] = [];
for (let [clientExport, serverExport] of MUTUALLY_EXCLUSIVE_ROUTE_EXPORTS) {
if (
toValidate.includes(clientExport) &&
toValidate.includes(serverExport)
) {
errors.push([clientExport, serverExport]);
}
}
if (errors.length > 0) {
throw new Error(
`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"),
);
}
}
type RouteChunks = ReturnType<typeof _detectRouteChunks>;
function detectRouteChunks(
cache: Cache,
id: string,
code: string,
isRootRouteModule: boolean,View on GitHub (pinned to 6beaca3952)