remix-run/react-router · error · ErrorResponseImpl
Route "${routeId}" does not have ${article} ${type}, but you
Error message
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 What it means
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.
Source
Thrown at packages/react-router/lib/dom/ssr/routes.tsx:223
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,
initialState: HydrationState | null,
ssr: boolean,
isSpaMode: boolean,
parentId: string = "",
routesByParentId: Record<
string,
Omit<EntryRoute, "children">[]
> = groupRoutesByParentId(manifest),
needsRevalidation?: Set<string>,
): DataRouteObject[] {
return (routesByParentId[parentId] || []).map((route) => {
let routeModule = routeModulesCache[route.id];
View on GitHub (pinned to 6beaca3952)
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.
Example fix
// before
export default function Notes() {
return <Form method="post"><input name="title" /><button>Save</button></Form>;
}
// after
export async function action({ request }: ActionFunctionArgs) {
const fd = await request.formData();
await saveNote(String(fd.get("title")));
return redirect("/notes");
}
export default function Notes() {
return <Form method="post"><input name="title" /><button>Save</button></Form>;
} Defensive patterns
Strategy: validation
Validate before calling
// before rendering an editable form, confirm the route can handle POST const canSubmit = Boolean(routeModule.action ?? routeModule.clientAction);
Try / catch
try {
await fetcher.submit(formData, { method: "post" });
} catch (e) {
if (isResponse(e) && e.status === 405) setFormError("This page cannot save yet.");
else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/f07f70ee69dce603.
Report an issue: GitHub.