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: "${route.id}")

What it means

In framework mode, `serverLoader()` and `serverAction()` are only callable from routes whose manifest entry has `hasLoader`/`hasAction` true (i.e., the route module actually exports `loader`/`action`). This guard runs before the single-fetch request is made, logs the message to the console, and throws an `ErrorResponseImpl(400, "Bad Request")` so it surfaces in the route's ErrorBoundary like any other loader/action error.

Source

Thrown at packages/react-router/lib/dom/ssr/routes.tsx:210

    groupRoutesByParentId(manifest),
    needsRevalidation,
  );
}

function preventInvalidServerHandlerCall(
  type: "action" | "loader",
  route: Omit<EntryRoute, "children">,
) {
  if (
    (type === "loader" && !route.hasLoader) ||
    (type === "action" && !route.hasAction)
  ) {
    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: "${route.id}")`;
    console.error(msg);
    throw new ErrorResponseImpl(400, "Bad Request", new Error(msg), true);
  }
}

export function noActionDefinedError(
  type: "action" | "clientAction",
  routeId: string,
) {
  let article = type === "clientAction" ? "a" : "an";
  let msg =
    `Route "${routeId}" does not have ${article} ${type}, but you are trying to ` +
    `submit to it. To fix this, please add ${article} \`${type}\` function to the route`;
  console.error(msg);
  throw new ErrorResponseImpl(405, "Method Not Allowed", new Error(msg), true);
}

export function createClientRoutes(
  manifest: RouteManifest<EntryRoute>,
  routeModulesCache: RouteModules,

View on GitHub (pinned to 6beaca3952)

Solutions

  1. Add the missing server export to the route: `export async function loader(args) { ... }` (or `action`).
  2. If the data is client-only, replace `serverLoader()` with your own fetching logic (e.g., fetch in clientLoader).
  3. If you just added the loader and still see the error, restart the dev server or rebuild so the manifest `hasLoader` flag is regenerated.
  4. Move the `serverLoader()` call out of components; it is only valid inside clientLoader/clientAction of a route that has the server handler.

Example fix

// before (route has clientLoader but no loader export)
export async function clientLoader() {
  return serverLoader();
}

// after
export async function loader({ request }: LoaderFunctionArgs) {
  return getUser(await requireUser(request));
}
export async function clientLoader() {
  return serverLoader();
}
Defensive patterns

Strategy: try-catch

Try / catch

let data;
try {
  data = await serverLoader();
} catch (e) {
  if (isResponse(e) && e.status === 400) {
    // route has no server loader — degrade gracefully
    data = await fetchLocalFallback();
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Calling `await serverLoader()` inside a `clientLoader` of a route that has no `loader` export; calling `serverAction()` from a component or clientAction of a route without an `action` export; removing a loader export but leaving the `serverLoader()` call in clientLoader; the manifest being stale after renaming/moving routes.

Common situations: Adding a clientLoader that optimistically calls serverLoader without adding the server loader; refactoring a loader into clientLoader-only and forgetting to delete the serverLoader call; HMR or a stale build manifest claiming a route has no loader right after you add one.

Related errors


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