remix-run/react-router · error

Module cannot have both a HydrateFallback export and a…

Error message

Module cannot have both a HydrateFallback export and a ServerHydrateFallback export

What it means

In RSC mode a route's loading UI can come from the client 'HydrateFallback' export or the server 'ServerHydrateFallback' export, never both. The framework-route wrapper picks the client variant first but throws when both are truthy, because which fallback renders during hydration would be ambiguous.

Solutions

  1. Keep only 'ServerHydrateFallback' for a server-rendered fallback
  2. Keep only 'HydrateFallback' for a client-only fallback
  3. Share fallback markup via a separate imported component

Example fix

// before
export function HydrateFallback() { return <Spinner />; }
export function ServerHydrateFallback() { return <ShellSpinner />; }

// after
export function HydrateFallback() { return <Spinner />; }
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

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

Common situations: Adding a server-rendered fallback to a route that already declares a client HydrateFallback (common when routes use clientLoader and the fallback was later moved server-side).

Related errors


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

Appendix: source

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

        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;
    }

    const {
      action,
      clientAction,
      clientLoader,
      clientMiddleware,
      handle,
      headers,
      links,
      loader,
      meta,

View on GitHub (pinned to 6beaca3952)