remix-run/react-router · error · ErrorResponseImpl

You are trying to call ${fn} on a route that does not have a

Error message

You are trying to call ${fn} on a route that does not have a server ${type} (routeId: "${routeId}")

What it means

In RSC mode, `serverLoader()`/`serverAction()` calls from client components are gated by `preventInvalidServerHandlerCall`, which checks the route's `hasLoader`/`hasAction` flags from the server manifest before issuing the fetch. When the flag is false it logs the message and throws an `ErrorResponseImpl(400, "Bad Request")`, surfacing through the nearest error boundary. It is the RSC equivalent of the framework-mode guard in routes.tsx.

Source

Thrown at packages/react-router/lib/rsc/browser.tsx:1022

}

function callSingleFetch(singleFetch: unknown) {
  invariant(typeof singleFetch === "function", "Invalid singleFetch parameter");
  return singleFetch();
}

function preventInvalidServerHandlerCall(
  type: "action" | "loader",
  routeId: string,
  hasHandler: boolean,
) {
  if (!hasHandler) {
    let fn = type === "action" ? "serverAction()" : "serverLoader()";
    let msg =
      `You are trying to call ${fn} on a route that does not have a server ` +
      `${type} (routeId: "${routeId}")`;
    console.error(msg);
    throw new ErrorResponseImpl(400, "Bad Request", new Error(msg), true);
  }
}

// Currently rendered links that may need prefetching
const nextPaths = new Set<string>();

// FIFO queue of previously discovered routes to prevent re-calling on
// subsequent navigations to the same path
const discoveredPathsMaxSize = 1000;
const discoveredPaths = new Set<string>();

function getManifestUrl(
  paths: string[],
  clientVersion: string | undefined,
): URL | null {
  if (paths.length === 0) {
    return null;
  }

View on GitHub (pinned to 7aea711dd1)

Solutions

  1. Add the missing `loader` (or `action`) export to the target route's module so the manifest marks it.
  2. Restart the dev server / rebuild so the RSC manifest reflects the new handler.
  3. If the data is fetched client-side only, stop calling `serverLoader()` and fetch directly.
  4. Confirm you are calling it from the route whose handler you added, not a sibling/child route.

Example fix

// before
// app/routes/todo.tsx (no loader export)
"use client";
export async function clientLoader() {
  return await serverLoader();
}

// after
// app/routes/todo.tsx
export async function loader() {
  return { items: await getTodos() };
}
"use client";
export async function clientLoader() {
  return await serverLoader();
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await serverLoader();
} catch (e) {
  if (isResponse(e) && e.status === 400) {
    return fetchClientSideFallback(); // route has no server loader
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `serverLoader()` in a route whose module has no `loader` export (e.g., a client-only route that assumed server data); calling `serverAction()` where only `clientAction` exists; stale manifests after adding a loader without reloading; moving a loader between routes and leaving the call behind.

Common situations: Converting pages to RSC and calling serverLoader from client components before wiring the server loader; hot-reload keeping an old manifest during development; renaming route files so the manifest id no longer carries the hasLoader flag.

Related errors


AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18). Data as JSON: /api/errors/14de23414ff12cac. Report an issue: GitHub.