{"record":{"id":"f07f70ee69dce603","repo":"remix-run/react-router","slug":"route-routeid-does-not-have-article-type","errorCode":null,"errorMessage":"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","messagePattern":"Route \"(.+?)\" does not have (.+?) (.+?), but you are trying to submit to it\\. To fix this, please add (.+?) `(.+?)` function to the route","errorType":"http","errorClass":"ErrorResponseImpl","httpStatus":405,"severity":"error","filePath":"packages/react-router/lib/dom/ssr/routes.tsx","lineNumber":223,"sourceCode":"    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,\n  initialState: HydrationState | null,\n  ssr: boolean,\n  isSpaMode: boolean,\n  parentId: string = \"\",\n  routesByParentId: Record<\n    string,\n    Omit<EntryRoute, \"children\">[]\n  > = groupRoutesByParentId(manifest),\n  needsRevalidation?: Set<string>,\n): DataRouteObject[] {\n  return (routesByParentId[parentId] || []).map((route) => {\n    let routeModule = routeModulesCache[route.id];\n","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/remix-run/react-router/blob/6beaca39526d5716c3c112ebb0782765baa5a9ce/packages/react-router/lib/dom/ssr/routes.tsx#L205-L241","documentation":"When a form submission (or fetcher submission) targets a route, React Router needs either a server `action` or a `clientAction` on that route to process it. `noActionDefinedError` logs the message and throws an `ErrorResponseImpl(405, \"Method Not Allowed\")`, which is rendered by the route's ErrorBoundary. The article (\"a\"/\"an\") and the function name in the message tell you exactly which export is missing.","triggerScenarios":"Rendering `<Form method=\"post\">` on a route with no `action` and no `clientAction`; a fetcher `fetcher.submit(..., { method: \"post\" })` pointed at a route without an action; posting to an index route whose parent has the action but the form targets the child; SPA-mode routes where only a clientLoader exists.","commonSituations":"Starting from a read-only page and adding a settings/logout form without adding an action; pointing `<Form action=\"/some-path\">` at a path whose route lacks an action; upgrading a Remix app where an action was accidentally dropped during refactor; GET submissions (`method=\"get\"`) are fine, so the error appears only after switching the form to POST.","solutions":["Add an `action` export to the route the form submits to: `export async function action({ request }: ActionFunctionArgs) { ... }`.","For client-side only handling, export `clientAction` instead.","Verify the form's `action` prop resolves to the route you think it does (check the matched route in dev tools).","If the action lives on a parent layout route, submit to that parent path explicitly."],"exampleFix":"// before\nexport default function Notes() {\n  return <Form method=\"post\"><input name=\"title\" /><button>Save</button></Form>;\n}\n\n// after\nexport async function action({ request }: ActionFunctionArgs) {\n  const fd = await request.formData();\n  await saveNote(String(fd.get(\"title\")));\n  return redirect(\"/notes\");\n}\nexport default function Notes() {\n  return <Form method=\"post\"><input name=\"title\" /><button>Save</button></Form>;\n}","handlingStrategy":"validation","validationCode":"// before rendering an editable form, confirm the route can handle POST\nconst canSubmit = Boolean(routeModule.action ?? routeModule.clientAction);","typeGuard":null,"tryCatchPattern":"try {\n  await fetcher.submit(formData, { method: \"post\" });\n} catch (e) {\n  if (isResponse(e) && e.status === 405) setFormError(\"This page cannot save yet.\");\n  else throw e;\n}","preventionTips":["Add the action export in the same change that adds the POST form.","Use TypeScript route module types; action presence is visible in the module you edit.","Wire route ErrorBoundaries to render actionable 405 messages."],"tags":["form-submission","action","http-405"],"backgroundTag":"http-405-method-not-allowed","analyzedSha":"6beaca39526d5716c3c112ebb0782765baa5a9ce","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}