remix-run/react-router · error · Error

Module cannot have both a Layout export and a ServerLayout e

Error message

Module cannot have both a Layout export and a ServerLayout export

What it means

Runtime guard in frameworkRoute() (virtual-route-config.ts): a route module cannot export both `Layout` (client) and `ServerLayout` (server). The wrapper picks one Layout slot; exporting both is ambiguous. Thrown at module-load time inside the lazy route resolver.

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 1fd704a7da)

Solutions

  1. Keep only one Layout export per module: `Layout` for client-rendered layouts, or `ServerLayout` for server-rendered layouts.
  2. If you need both behaviors, split into two route modules or use a shared child component imported by each.
  3. Regenerate types to get the build-time duplicate check.

Example fix

// app/routes/_layout.tsx
// before
export function Layout({ children }) { return <div className="client">{children}</div>; }
export function ServerLayout({ children }) { return <div className="server">{children}</div>; }
// after (server layout only)
export function ServerLayout({ children }) { return <div className="server">{children}</div>; }
Defensive patterns

Strategy: validation

Validate before calling

const src = readFileSync(layoutFile, 'utf8');
const hasLayout = /export\s+(async\s+)?function\s+Layout\b/.test(src);
const hasServerLayout = /export\s+(async\s+)?function\s+ServerLayout\b/.test(src);
if (hasLayout && hasServerLayout) throw new Error(`${layoutFile}: cannot export both Layout and ServerLayout`);

Type guard

function moduleExportsOnlyOneLayout(mod: Record<string, unknown>): boolean {
  return !mod.Layout || !mod.ServerLayout;
}

Prevention

When it happens

Trigger: A layout route module (e.g. app/routes/_layout.tsx) exports both `Layout` and `ServerLayout`. The `if ('Layout' in mod && mod.Layout)` branch detects the coexistence and throws.

Common situations: Splitting a layout into client/server variants and leaving both exports. Migrating a Framework Mode layout to RSC Framework Mode without removing the client Layout.

Related errors


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