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
- Add the missing `loader` (or `action`) export to the target route's module so the manifest marks it.
- Restart the dev server / rebuild so the RSC manifest reflects the new handler.
- If the data is fetched client-side only, stop calling `serverLoader()` and fetch directly.
- 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
- Add the server loader/action export before wiring serverLoader()/serverAction() calls.
- Restart the dev server after changing route handlers so the RSC manifest updates.
- Keep handler and call site in the same route module.
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
- You are trying to call ${fn} on a route that does not have a
- No response body
- Unexpected payload type
- The "@vitejs/plugin-rsc" plugin should be placed after the R
- When using the React Router `basename` and the Vite `base` c
AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18).
Data as JSON: /api/errors/14de23414ff12cac.
Report an issue: GitHub.