I ran into a small issue working with forms in index routes with Remix. I was working on an application that submitted a form on the default index route app/routes/index.tsx. I created an action function to handle the form submission, but when I submitted the form Remix would return a 405 Method Not Allowed response.

Following is a sample application that demonstrates the issue.

import type { ActionFunction } from "@remix-run/server-runtime";
import { useActionData } from "@remix-run/react";

export let action: ActionFunction = async ({ request }) => {
  let formData = await request.formData();
  let name = formData.get("name");

  return { name };

export default function Index() {
  let data = useActionData<{ name?: string }>();
  let name = data?.name;

  return (
      {name ? <h1>{`Hello, ${name}!`}</h1> : null}
      <form method="post">
        <input type="text" name="name" />
        <button type="submit">Greet</button>

So what is happening? Remix routes are a tree and so it needs to disambiguate between a root layout route and a child route. The sample above provides the action in an index route but does not provide a hint to Remix, so Remix assumes the action is defined in the parent layout route (app/root.tsx in this case).

Remix uses the presence of a query parameter called index as a hint that it should not use the parent layout root but should instead use the index route for handling an action. See the Remix docs for more details. We can either provide the hint manually or use the Form component which provides the hint automatically.

To provide the hint manually, change the form element to an attribute action="?index" like in the following code.

<form method="post" action="?index">
  <input type="text" name="name" />
  <button type="submit">Greet</button>

Using the Remix Form element not only provides this automatically but also provides an enctype="application/x-www-form-urlencoded". I will probably just use the provided Form element by default moving forward.