Next.js v 14.2.4 405 Method not Allowed on Form Submission

my deployed site is: https://atlasbhw-v2.netlify.app/

i get a 404 error, failed to load resource /__forms.html

i followed the docs, as well as several posts with the same issue, but their solutions havent worked for me. The example in the docs dont use typescript so i had to add those, other than that I just dont understand whats missing. the deployed site gives me a 404 error, localhost says 405 method not allowed. Ive commented out my header and just have enctype="multipart/form-data" in __forms.html. When i navigate to https://atlasbhw-v2.netlify.app/__forms.html i get a blank page, but when you inspect it you do see the hidden form.

UPDATE I looked into what 405 means and decided to update the fetch call to ‘/’ instead of ‘/__forms.html’, this give me a 200 success on localhost and it looks like my forms submits, however i never see the form submission in netlify (i cant remember if they show when you submit through localhost), trying the deployed site after pushing this change and i get my error message and in the console i see the same “Failed to load resource: the server responded with a status of 405 ()” error that i was getting previously in localhost. What else do I need to do? Which is supposed to be correct fetch call to ‘/’ or fetch call to ‘/__forms.html’? The latter seemed to work the least so im confused and it seems these threads all have slightly different stitched together solutions, theres gotta be a concrete ‘right’ way?! Or is this not possible with this version of next.js? Downgrading is not an option, I was excited to see Netlify had been updated as I prefer this over Vercel, but not if doesnt really work.

//__forms.html file

<html>
  <head></head>
  <body>
    <form
      name="atlasbhw-jobs"
      data-netlify="true"
      data-netlify-recaptcha="true"
      netlify-honeypot="bot-field"
      enctype="multipart/form-data"
      hidden
    >
      <input type="hidden" name="form-name" value="atlasbhw-jobs" />
      <input name="name" type="text" />
      <input name="email" type="email" />
      <input name="phone" type="text" />
      <input name="coverLetter" type="file" />
      <input name="resume" type="file" />
      <div data-netlify-recaptcha="true"></div>
      <button type="submit">Apply</button>
    </form>
  </body>
</html>

// Form

'use client'
import React, { useState } from "react";
import { BiCheckCircle } from "react-icons/bi";

type Props = {
  requestResume: boolean;
  requestCoverLetter: boolean;
};

const JobApplicationForm = ({ requestCoverLetter, requestResume }: Props) => {
  const [status, setStatus] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const handleFormSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      setStatus("pending");
      setError(null);
      const myForm = event.target as HTMLFormElement;
      const formData = new FormData(myForm);
      const params = new URLSearchParams();
      formData.forEach((value, key) => {
        params.append(key, value.toString());
      });
      const res = await fetch("/__forms.html", {
        method: "POST",
        body: params.toString(),
      });
      if (res.status === 200) {
        setStatus("ok");
      } else {
        setStatus("error");
        setError(`${res.status} ${res.statusText}`);
      }
    } catch (e) {
      setStatus("error");
      setError(`${e}`);
    }
  };

  return (
    <form
      onSubmit={handleFormSubmit}
      name="atlasbhw-jobs"
      className="w-[90vw] max-w-prose flex flex-col"
      data-netlify="true"
      data-netlify-recaptcha="true"
      hidden
    >
      <div className="flex flex-col mb-3">
        <label htmlFor="name">Name: *</label>
        <input
          type="text"
          name="name"
          id="name"
          className="border p-1 rounded-md"
          placeholder="Full Name"
          required
        />
      </div>
      <div className="flex flex-col mb-3">
        <label htmlFor="email">Email: *</label>
        <input
          type="email"
          name="email"
          id="email"
          className="border p-1 rounded-md"
          placeholder="name@email.com"
          required
        />
      </div>
      <div className="flex flex-col mb-3">
        <label htmlFor="phone">Phone: *</label>
        <input
          type="text"
          name="phone"
          id="phone"
          placeholder="(XXX)-XXX-XXXX"
          className="border p-1 rounded-md"
          required
        />
      </div>
      {requestCoverLetter && (
        <div className="flex flex-col mb-3">
          <label htmlFor="coverLetter">Cover Letter: *</label>
          <input
            type="file"
            name="coverLetter"
            className="border p-1 rounded-md"
            id="coverLetter"
            required
            accept=".pdf,.doc,.docx"
          />
        </div>
      )}
      {requestResume && (
        <div className="flex flex-col">
          <label htmlFor="resume">Resume: *</label>
          <input
            type="file"
            name="resume"
            id="resume"
            className="border p-1 rounded-md"
            accept=".pdf,.doc,.docx"
            required
          />
        </div>
      )}
      <div data-netlify-recaptcha="true" />
      <button
        className="px-6 py-2 mt-6 self-start bg-accent text-white rounded-md"
        type="submit"
      >
        {status === "ok" ? (
          <>
            Submitted! <BiCheckCircle className="text-green-500" />
          </>
        ) : (
          "Apply"
        )}
      </button>
      <p className="text-red-500">{error}</p>
    </form>
  );
};

export default JobApplicationForm;

@artsycoder533 What’s the URL for the “Job Application Form”?

Hi, its Atlas Behavioral Health + Wellness

It may seem that way, but it’s not the case, it’s ultimately always the same solution.

What matters is that:

  • The form exists somewhere in static HTML output that Netlify’s post-processing can detect
  • The submission is made to a URL that actually exists as a truly static file

The name of that file doesn’t matter in the slightest, it could be __forms.html or it could be /wizzle/wuzzle/the-file-name-does-not-matter.html.

These requirements exist because Netlify forms was originally constructed to work with standard HTML pages, and subsequently rely on what you could think of as Netlify’s “default handling”.

Next.js has all kinds of fancy functionality that inserts itself “in front of” that default handling, and you end up in a situation where you find you cannot post your form to your site routes, because they’re being handled by Next.js and never actually reaching the original “default handling”.

Currently your form is posting to / and that route is being handled by Next.js

See the X-Powered-By is Next.js

In this case, since you have the static HTML form as /__forms.html, then that is what you want to make your fetch request to, to ensure it isn’t caught by Next.js and that Netlify handle it.

If you then encounter other issues, then we can work through those separately.

I posted an update probably the same time you were typing your first response. I originally tried sending it to __forms.html and it didnt even send through localhost so I tried something else. Ill put it back maybe you can see something I cant but i still got an error nonetheless.

@artsycoder533 I can’t advise regarding localhost as I do not work for Netlify, have never used their local development tooling, and otherwise have no idea what you’re doing.

I can only advise how forms work when deployed to Netlify, having successfully helped many people get their forms going.

Note: I read your update while posting my response, I even quoted directly from it.

I understand, you suggested what the docs said, but that just didnt work for me and I thought i followed the steps.

  const res = await fetch("/__forms.html", {
        method: "POST",
        body: params.toString(),
      });

Adding it back leaves me with this error:
Screenshot 2024-08-07 at 10.44.17 PM

and localhost no longer has a successful submission

the docs are a happy path, i need a what do i now type of thing, that answer i cant seem to find

@artsycoder533 I understand you’ve already tried submitting to /__forms.html and that it didn’t work, and that it’s in the documentation, but it’s still the correct step for you to perform first.

As mentioned I cannot advise regarding localhost, I may never be able to assist you with that, so I’m ignoring it.

Believe it or not, the change to /__forms.html has you closer to getting it working.

Now as I mentioned, we can work through the follow on issue.
Checking your form, posting to /__forms.html does indeed result in 404.

Yet visiting that URL we can confirm that the page does exist:
https://atlasbhw-v2.netlify.app/__forms.html

Most importantly, it has response headers that indicate it’s an actual file, not handled by Next.js

So we know that doing a POST to it will encounter Netlify’s “default handling”.

The mystery then becomes “why does a file that we know exists, return a 404”?

The answer is that the form-name is extremely important, and your submission is not providing it:

You’ll see it mentioned throughout the documentation:
https://docs.netlify.com/forms/setup/#submit-javascript-rendered-forms-with-ajax
https://docs.netlify.com/frameworks/next-js/overview/

Viewing your static file:

You should be sending form-name as atlasbhw-jobs

Ah, I thought that was literally a variable that you had to put called form-name and the value was the name of your form. I still get the same 404 error though.

<form
      name="atlasbhw-jobs"
      data-netlify="true"
      data-netlify-recaptcha="true"
      netlify-honeypot="bot-field"
      enctype="multipart/form-data"
      hidden
    >
      <input type="hidden" name="atlasbhw-jobs" value="atlasbhw-jobs" />
      <input name="name" type="text" />
      <input name="email" type="email" />
      <input name="phone" type="text" />
      <input name="coverLetter" type="file" />
      <input name="resume" type="file" />
      <div data-netlify-recaptcha="true"></div>
      <button type="submit">Apply</button>
    </form>

That’ll be because whatever you’ve changed, hasn’t changed what matters.

Whatever code your form submission is occurring via, it is still not sending form-name:

With Netlify’s default handling it’s as per the documentation:

When Netlify parses the static HTML for a form you’ve added, the build system automatically strips the data-netlify="true" or netlify attribute from the <form> tag and injects a hidden input named form-name . In the resulting HTML that’s deployed, the data-netlify="true" or netlify attribute is gone, and the hidden form-name input’s value matches the name attribute of <form>

Which is why if you view the source on https://atlasbhw-v2.netlify.app/__forms.html you’ll see that it has:

Which it would have, even if you hadn’t specified it yourself.

However that form exists for only two reasons:

  1. Form detection - so Netlify’s post processing knows the form exists, and what fields it has
  2. As a known static route - it wouldn’t matter if you posted to any static route, this one isn’t special

That form isn’t actually what is being submitted though, you’re no doubt doing that via AJAX in your Next.js code, you need to make sure your Next.js form submission fetch request is adjusted so that it contains the form-name.

If it’s just pulling all the fields from your Next.js form, you’ll find there is no form-name input in that form.

So I added the hidden input with the form name to my actual form in my Next component and same error persists. If thats not it where does it go? Also this is so confusing should it be:

<input type="hidden" name="form-name" value="atlasbhw-jobs" />

or

<input type="hidden" name="atlasbhw-jobs" value="atlasbhw-jobs" />

my payload is:

atlasbhw-jobs=atlasbhw-jobs&name=natasha&email=email%40email.com&phone=5555555555&coverLetter=%5Bobject+File%5D&resume=%5Bobject+File%5D

is it supposed to be form-name=atlasbhw-jobs ?

at this point ive tried both ways, im still the same error this is so frustrating

    <form
      onSubmit={handleFormSubmit}
      name="atlasbhw-jobs"
      className="w-[90vw] max-w-prose flex flex-col"
      data-netlify="true"
      data-netlify-recaptcha="true"
    >
        <input type="hidden" name="atlasbhw-jobs" value="atlasbhw-jobs" />
      <div className="flex flex-col mb-3">
        <label htmlFor="name">Name: *</label>
        <input
          type="text"
          name="name"
          id="name"
          className="border p-1 rounded-md"
          placeholder="Full Name"
          required
        />
      </div>
      <div className="flex flex-col mb-3">
        <label htmlFor="email">Email: *</label>
        <input
          type="email"
          name="email"
          id="email"
          className="border p-1 rounded-md"
          placeholder="name@email.com"
          required
        />
      </div>
      <div className="flex flex-col mb-3">
        <label htmlFor="phone">Phone: *</label>
        <input
          type="text"
          name="phone"
          id="phone"
          placeholder="(XXX)-XXX-XXXX"
          className="border p-1 rounded-md"
          required
        />
      </div>
      {requestCoverLetter && (
        <div className="flex flex-col mb-3">
          <label htmlFor="coverLetter">Cover Letter: *</label>
          <input
            type="file"
            name="coverLetter"
            className="border p-1 rounded-md"
            id="coverLetter"
            required
            accept=".pdf,.doc,.docx"
          />
        </div>
      )}
      {requestResume && (
        <div className="flex flex-col">
          <label htmlFor="resume">Resume: *</label>
          <input
            type="file"
            name="resume"
            id="resume"
            className="border p-1 rounded-md"
            accept=".pdf,.doc,.docx"
            required
          />
        </div>
      )}
      <div data-netlify-recaptcha="true" />
      <button
        className="px-6 py-2 mt-6 self-start bg-accent text-white rounded-md"
        type="submit"
      >
        Apply
      </button>
      <p className="flex items-center gap-4">
        {status === "ok" ? (
          <>
            Submitted! Thank you for your application! <BiCheckCircle className="text-2xl text-green-500" />
          </>
        ) : null}
      </p>
      <p className="text-red-500">
        {error ? "Error submitting form, please try again" : null}
      </p>
    </form>

i do see the input now on the /getstarted-join-us page at least
Screenshot 2024-08-07 at 11.49.35 PM

@artsycoder533 The correct key is form-name and the value would be whichever form is being submitted, in this case atlasbhw-jobs.

So the payload should include form-name=atlasbhw-jobs.

I’d expect what you have currently with atlasbhw-jobs=atlasbhw-jobs to result in a 404 as Netlify is looking explicitly for form-name.

Set it to that, and then we can see if something else is occuring.

You can be making progress and it still result in an error, just have to keep fixing things till everything is fixed.

both hidden inputs in my __forms.html file and my nextjs component form are:

<input type="hidden" name="form-name" value="atlasbhw-jobs" />

payload:

form-name=atlasbhw-jobs&name=natasha&email=email%40email.com&phone=5555555555&coverLetter=%5Bobject+File%5D&resume=%5Bobject+File%5D

result:

       POST https://atlasbhw-v2.netlify.app/__forms.html 404 (Not Found)

@artsycoder533 Yes, I’ve checked and confirmed that as you’d already mentioned it’s still being given a 404 response, which means there is also some other issue occurring.

But it doesn’t negate the steps we’ve done, they all still needed to happen.

The new question becomes, why is Netlify’s default handling still unhappy with what’s being sent?

Most people that I’ve assisted aren’t doing “file uploads”, so my first wild guess is that it’s probably related to that.

Checking the documentation they have:
https://docs.netlify.com/forms/setup/#submit-file-uploads-with-ajax

The crux of which is:

fetch("/", {
  body: new FormData(event.target),
  method: "POST",
})

You have:

const formData = new FormData(myForm);
const params = new URLSearchParams();
formData.forEach((value, key) => {
  params.append(key, value.toString());
});
const res = await fetch("/__forms.html", {
  method: "POST",
  body: params.toString(),
});

Which looks… very incorrect.

So it’s not occurring because you’re submitting files.
It’s that you aren’t submitting correct data at all.

my project is using typescript, its not happy about passing in formData, all i did was a work around to infer the type to make the error go away.

Argument of type 'FormData' is not assignable to parameter of type 'string | string[][] | Record<string, string> | URLSearchParams | undefined'.
  Type 'FormData' is missing the following properties from type 'URLSearchParams': size,

the build fails due to the typescript error. keeping it this way im forced to use formData as any which is not ideal. I humored myself though and the 404 error continues

I even commented out the file uploads, which is the whole point of me needing a form, and i still get the 404 error.

@artsycoder533 I can’t advise anything regarding TypeScript, I do not work with it myself, so while I’m familiar with type errors (from starting my career long ago as an AS3 developer), I am not familiar with its specific tantrums.

What’s important, for Netlify’s handling, is that the request be made correctly.

How that request is created largely doesn’t matter e.g. You should be able to test this with a cURL

Checking your /__forms.html file I notice that you’ve commented out the file fields.

However, testing that form directly I received a successful submission:

You should see the values I sent in your Netlify UI.

yea i see the submission, but the form still gives a 404 the normal way a user would see it. i need file uploads, so are you saying file uploads dont work, because i need to use another service then, my form is useless without them. im sorry im still missing the why as to whats happening.

I’d expect it does, and it will continue to, until it provides a valid submission.

That’s fine, you should enable them on the the static file and we can test them there.

No, I never said that.

My theory is your submission is incorrect, sorry that I’m not able to tell you immediately precisely how, but it’s code that you’ve written.

We’ll be able to sort it out by stepping through, as is the usual case for debugging things, but if you don’t want to do that and believe your code is without fault and want to try another service, then you’re free to do that.

I’m happy to help you, but I also don’t work for Netlify as mentioned and don’t particularly care what you use, whatever works!

im not saying its without fault its not working, i need this specific use case to give me a successful form submission, im just saying so far this isnt working, so at some point i will have to look elsewhere

my function

  const handleFormSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    try {
      setStatus("pending");
      setError(null);
      const myForm = event.target as HTMLFormElement;
      //   const formData = new FormData(myForm);
      const formData = new FormData(myForm);
      //   const params = new URLSearchParams();
      //   formData.forEach((value, key) => {
      //     params.append(key, value.toString());
      //   });
      const res = await fetch("/__forms.html", {
        method: "POST",
        // body: params.toString(),
        body: new URLSearchParams(formData as any).toString(),
      });
      if (res.status === 200) {
        setStatus("ok");
        (myForm as HTMLFormElement).reset();
      } else {
        setStatus("error");
        setError(`${res.status} ${res.statusText}`);
      }
    } catch (e) {
      setStatus("error");
      setError(`${e}`);
    }
  };

this is from the code example they link you to in the docs: next-platform-starter/components/feedback-form.jsx at main ¡ netlify-templates/next-platform-starter ¡ GitHub

 const handleFormSubmit = async (event) => {
        event.preventDefault();
        try {
            setStatus('pending');
            setError(null);
            const myForm = event.target;
            const formData = new FormData(myForm);
            const res = await fetch('/__forms.html', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: new URLSearchParams(formData).toString()
            });
            if (res.status === 200) {
                setStatus('ok');
            } else {
                setStatus('error');
                setError(`${res.status} ${res.statusText}`);
            }
        } catch (e) {
            setStatus('error');
            setError(`${e}`);
        }
    };

the only difference i see is i added a type and omitted the header as i see several posts say to remove it due to file submissions and add enctype="multipart/form-data" instead. im simply trying to find out is this possible or not, an example of someone with the same use case would be fantastic but i havent found it, you can try but i dont know what else to do