remix-run/react-router · error

Module cannot have both an ErrorBoundary export and a…

Error message

Module cannot have both an ErrorBoundary export and a ServerErrorBoundary export

What it means

In RSC mode a route's error UI can come from the client 'ErrorBoundary' export or the server 'ServerErrorBoundary' export, but not both. The framework-route wrapper throws when both are truthy to prevent an ambiguous error-rendering path — this fires whenever the route module is lazily loaded and both exports exist.

Solutions

  1. Remove 'ErrorBoundary' if errors for this route should render on the server
  2. Remove 'ServerErrorBoundary' if the error UI must be interactive on the client
  3. Keep shared error markup in a separate module imported by the surviving export

Example fix

// before
export function ErrorBoundary() { return <p>Something broke</p>; }
export function ServerErrorBoundary() { return <p>Server error</p>; }

// after
export function ServerErrorBoundary() { return <p>Server error</p>; }
Defensive patterns

Strategy: validation

Validate before calling

const PAIRS = [["ErrorBoundary", "ServerErrorBoundary"]] 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 hasErrorBoundaryConflict(mod: Record<string, unknown>): boolean {
  return Boolean(mod.ErrorBoundary) && Boolean(mod.ServerErrorBoundary);
}

Prevention

When it happens

Trigger: A route module exporting both 'export function ErrorBoundary()' and 'export function ServerErrorBoundary()' with both truthy.

Common situations: Adding a server error boundary to a route that already had a client one; copying an RSC example's ServerErrorBoundary into an existing route without removing the old export.

Related errors


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

Appendix: 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 6beaca3952)