remix-run/react-router · error · Error

Module cannot have both an ErrorBoundary export and a Server

Error message

Module cannot have both an ErrorBoundary export and a ServerErrorBoundary export

What it means

Runtime guard in frameworkRoute(): a route module cannot export both `ErrorBoundary` (client) and `ServerErrorBoundary` (server). Thrown when both are truthy at module load inside the lazy resolver. The framework picks one error boundary per route; coexistence is ambiguous.

Source

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

    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",
        );
      }
      ErrorBoundary = mod.ErrorBoundary;
    } else if ("ServerErrorBoundary" in mod && mod.ServerErrorBoundary) {
      ErrorBoundary = mod.ServerErrorBoundary;
    }
    if ("HydrateFallback" in mod && mod.HydrateFallback) {
      if ("ServerHydrateFallback" in mod && mod.ServerHydrateFallback) {
        throw new Error(
          "Module cannot have both a HydrateFallback export and a ServerHydrateFallback export",
        );
      }
      HydrateFallback = mod.HydrateFallback;
    } else if ("ServerHydrateFallback" in mod && mod.ServerHydrateFallback) {
      HydrateFallback = mod.ServerHydrateFallback;
    }

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Remove one of the two exports — typically keep `ServerErrorBoundary` for RSC routes, or `ErrorBoundary` for client-only routes.
  2. Run typegen to get the duplicate-export compile-time error and fix all flagged modules.

Example fix

// app/routes/foo.tsx
// before
export function ErrorBoundary() { return <p>client err</p>; }
export function ServerErrorBoundary() { return <p>server err</p>; }
// after (server only)
export function ServerErrorBoundary() { return <p>server err</p>; }
Defensive patterns

Strategy: validation

Validate before calling

const src = readFileSync(routeFile, 'utf8');
const hasE = /export\s+(async\s+)?function\s+ErrorBoundary\b/.test(src);
const hasSE = /export\s+(async\s+)?function\s+ServerErrorBoundary\b/.test(src);
if (hasE && hasSE) throw new Error(`${routeFile}: cannot export both ErrorBoundary and ServerErrorBoundary`);

Type guard

function moduleExportsOnlyOneErrorBoundary(mod: Record<string, unknown>): boolean {
  return !mod.ErrorBoundary || !mod.ServerErrorBoundary;
}

Prevention

When it happens

Trigger: A route module exports `export function ErrorBoundary()` and `export function ServerErrorBoundary()` simultaneously. The `if ('ErrorBoundary' in mod && mod.ErrorBoundary)` branch detects the pair and throws.

Common situations: Migrating an error boundary to RSC and leaving the client version. Copying a sample that had both for documentation purposes.

Related errors


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/59b23f9a050c19e9. Report an issue: GitHub.