remix-run/react-router · error

Module cannot have both a Layout export and a ServerLayout…

Error message

Module cannot have both a Layout export and a ServerLayout export

What it means

In RSC mode a route's layout can be supplied either by the client 'Layout' export or by the server 'ServerLayout' export. The framework-route wrapper resolves them in order — client 'Layout' first — and throws when both are truthy, since rendering would be ambiguous between a client layout and a server layout for the same route.

Solutions

  1. Keep only 'ServerLayout' when the layout must run on the server
  2. Keep only 'Layout' when the layout needs state/effects on the client
  3. Extract shared markup into a component module imported by whichever layout remains

Example fix

// before
export function Layout({ children }) { return <div>{children}</div>; }
export function ServerLayout({ children }) { return <Shell>{children}</Shell>; }

// after
export function ServerLayout({ children }) { return <Shell>{children}</Shell>; }
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: A route module exporting both 'export function Layout({ children })' and 'export function ServerLayout({ children })' with both truthy.

Common situations: Migrating a layout route to server-side rendering by adding ServerLayout on top of an existing Layout export; merging two example route files during RSC adoption.

Related errors


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

Appendix: source

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

  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",
        );
      }
      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(

View on GitHub (pinned to 6beaca3952)