remix-run/react-router · error

Module cannot have both a default export and a…

Error message

Module cannot have both a default export and a ServerComponent export

What it means

In RSC mode, route modules are split into client and server portions, and a route's component can be supplied either by the client 'default' export or by the server 'ServerComponent' export — never both. The generated framework-route wrapper checks the module at render time: when 'default' is truthy and 'ServerComponent' is also present it throws, because it would be ambiguous which component should render.

Solutions

  1. Delete the 'default' export if the route should render on the server
  2. Delete 'ServerComponent' if the route must stay a client component
  3. Move any shared JSX/helpers into a separate imported module so only one component export remains

Example fix

// before — app/routes/dashboard.tsx
export default function Route() { return <h1>Client</h1>; }
export async function ServerComponent() { return <h1>Server</h1>; }

// after
export async function ServerComponent() { return <h1>Server</h1>; }
Defensive patterns

Strategy: validation

Validate before calling

// dev-time scan: import each route module and check export pairs
const PAIRS = [["default", "ServerComponent"]] as const;
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}: export either ${client} or ${server}, not both`);
    }
  }
}

Type guard

function hasComponentConflict(mod: Record<string, unknown>): boolean {
  return Boolean(mod.default) && Boolean(mod.ServerComponent);
}

Prevention

When it happens

Trigger: A route module containing both 'export default function Route()' and 'export async function ServerComponent()' (both truthy at module-evaluation time in the lazy-loaded route).

Common situations: Converting an existing Framework-mode route to RSC by adding ServerComponent while forgetting to delete the old default export; copy-pasting from examples that mix client and server component conventions in one file.

Related errors


AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18). Data as JSON: /api/errors/a721cd6ac55d6210. Report an issue: GitHub.

Appendix: source

Thrown at packages/react-router-dev/vite/rsc/virtual-route-config.ts:24

export function createVirtualRouteConfig({
  appDirectory,
  routeConfig,
}: {
  appDirectory: string;
  routeConfig: RouteConfigEntry[];
}): { code: string; routeIdByFile: Map<string, string> } {
  let routeIdByFile = new Map<string, string>();
  let code = js`import * as React from "react";
function frameworkRoute(lazy) {
  return async () => {
    const mod = await lazy();
    let Component;
    let Layout;
    let ErrorBoundary;
    let HydrateFallback;
    if ("default" in mod && mod.default) {
      if ("ServerComponent" in mod && mod.ServerComponent) {
        throw new Error("Module cannot have both a default export and a ServerComponent export");
      }
      Component = mod.default;
    } else if ("ServerComponent" in mod && mod.ServerComponent) {
      Component = mod.ServerComponent;
    }
    if ("Layout" in mod && mod.Layout) {
      if ("ServerLayout" in mod && mod.ServerLayout) {
        throw new Error("Module cannot have both a Layout export and a ServerLayout export");
      }
      Layout = mod.Layout;
    } else if ("ServerLayout" in mod && mod.ServerLayout) {
      Layout = mod.ServerLayout;
    }
    if ("ErrorBoundary" in mod && mod.ErrorBoundary) {
      if ("ServerErrorBoundary" in mod && mod.ServerErrorBoundary) {
        throw new Error(
          "Module cannot have both an ErrorBoundary export and a ServerErrorBoundary export",
        );

View on GitHub (pinned to 6beaca3952)