{"record":{"id":"1b65ca6f2539130b","repo":"remix-run/react-router","slug":"you-are-trying-to-call-fn-on-a-route-that-does","errorCode":null,"errorMessage":"You are trying to call ${fn} on a route that does not have a server ${type} (routeId: \"${route.id}\")","messagePattern":"You are trying to call (.+?) on a route that does not have a server (.+?) \\(routeId: \"(.+?)\"\\)","errorType":"http","errorClass":"ErrorResponseImpl","httpStatus":400,"severity":"error","filePath":"packages/react-router/lib/dom/ssr/routes.tsx","lineNumber":210,"sourceCode":"    groupRoutesByParentId(manifest),\n    needsRevalidation,\n  );\n}\n\nfunction preventInvalidServerHandlerCall(\n  type: \"action\" | \"loader\",\n  route: Omit<EntryRoute, \"children\">,\n) {\n  if (\n    (type === \"loader\" && !route.hasLoader) ||\n    (type === \"action\" && !route.hasAction)\n  ) {\n    let fn = type === \"action\" ? \"serverAction()\" : \"serverLoader()\";\n    let msg =\n      `You are trying to call ${fn} on a route that does not have a server ` +\n      `${type} (routeId: \"${route.id}\")`;\n    console.error(msg);\n    throw new ErrorResponseImpl(400, \"Bad Request\", new Error(msg), true);\n  }\n}\n\nexport function noActionDefinedError(\n  type: \"action\" | \"clientAction\",\n  routeId: string,\n) {\n  let article = type === \"clientAction\" ? \"a\" : \"an\";\n  let msg =\n    `Route \"${routeId}\" does not have ${article} ${type}, but you are trying to ` +\n    `submit to it. To fix this, please add ${article} \\`${type}\\` function to the route`;\n  console.error(msg);\n  throw new ErrorResponseImpl(405, \"Method Not Allowed\", new Error(msg), true);\n}\n\nexport function createClientRoutes(\n  manifest: RouteManifest<EntryRoute>,\n  routeModulesCache: RouteModules,","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/remix-run/react-router/blob/6beaca39526d5716c3c112ebb0782765baa5a9ce/packages/react-router/lib/dom/ssr/routes.tsx#L192-L228","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Add the missing server export to the route: `export async function loader(args) { ... }` (or `action`).","If the data is client-only, replace `serverLoader()` with your own fetching logic (e.g., fetch in clientLoader).","If you just added the loader and still see the error, restart the dev server or rebuild so the manifest `hasLoader` flag is regenerated.","Move the `serverLoader()` call out of components; it is only valid inside clientLoader/clientAction of a route that has the server handler."],"exampleFix":"// before (route has clientLoader but no loader export)\nexport async function clientLoader() {\n  return serverLoader();\n}\n\n// after\nexport async function loader({ request }: LoaderFunctionArgs) {\n  return getUser(await requireUser(request));\n}\nexport async function clientLoader() {\n  return serverLoader();\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let data;\ntry {\n  data = await serverLoader();\n} catch (e) {\n  if (isResponse(e) && e.status === 400) {\n    // route has no server loader — degrade gracefully\n    data = await fetchLocalFallback();\n  } else {\n    throw e;\n  }\n}","preventionTips":["Treat serverLoader()/serverAction() as paired with a loader/action export — add both in the same commit.","Let the 400 surface through the route ErrorBoundary instead of crashing the tree.","After adding or moving loaders, restart the dev server so hasLoader flags in the manifest refresh."],"tags":["server-loader","server-action","framework-mode","http-400"],"backgroundTag":"server-handler-not-defined","analyzedSha":"6beaca39526d5716c3c112ebb0782765baa5a9ce","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}