TidyURLs
Go to Tools
BLOG
The TidyURLsJournal

Practical guides, tutorials, and insights about URLs, SEO, web development, and online marketing. Learn, troubleshoot, and get the most of the web.

Back to the Journal
TidyURLs Journal

Why Your Next.js API Route Returns 405 Method Not Allowed

Practical insights, clear explanations, and useful ideas for understanding the web.

October 26, 20267 min read5 topics
Why Your Next.js API Route Returns 405 Method Not Allowed
Explore the details
#Next.js#API routes#HTTP methods#fetch API#web development
October 26, 20267 min read

A 405 Method Not Allowed response means a server received your request but does not allow that HTTP method for the requested URL. In Next.js, a common cause is sending GET to a route that only handles POST, or making a fetch() request without setting the method you intended. This guide shows how to diagnose the mismatch, write a route that handles GET and POST, and test the response.

Key Takeaways

  • In the App Router, a route.ts file handles a method only if it exports a matching function such as GET or POST.
  • fetch() uses GET by default. Set method: "POST" and provide a JSON body when sending a POST request.
  • A 405 is different from a 404: the path may exist, but the method is not supported there.
  • Check the actual request method, URL, and response status. A fetch() promise does not reject just because the server returned 405.

What a 405 Means in Next.js

HTTP methods describe what a client is asking a server to do. A browser address bar sends a GET request. A form or JavaScript client might send POST, PUT, or another method.

In the Next.js App Router, a Route Handler is defined in a route.js or route.ts file inside the app directory. It handles requests for the URL corresponding to that file. Export a function named for each method the route should accept. Supported method names include GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.

For example, app/api/items/route.ts handles requests to /api/items. If it exports only POST, a GET request to that path is a method mismatch. Next.js can respond with 405 Method Not Allowed when a method is not implemented by the Route Handler.

A 404 Not Found usually points to a URL that does not match a route. A 400 Bad Request usually means the server could not accept the request data. A 405 points you first toward the method.

Add GET and POST Handlers

Create app/api/items/route.ts:

export async function GET() {
  return Response.json({ items: [{ id: 1, name: "Notebook" }] });
}

export async function POST(request: Request) {
  let body: unknown;

  try {
    body = await request.json();
  } catch {
    return Response.json(
      { error: "Request body must be valid JSON" },
      { status: 400 }
    );
  }

  if (
    typeof body !== "object" ||
    body === null ||
    !("name" in body) ||
    typeof body.name !== "string" ||
    !body.name.trim()
  ) {
    return Response.json(
      { error: "A non-empty name is required" },
      { status: 400 }
    );
  }

  return Response.json(
    { item: { id: 2, name: body.name.trim() } },
    { status: 201 }
  );
}

This example returns a sample item from GET. The POST handler validates JSON and returns a created-style response with status 201. It does not save data; connect it to your database or other storage if the route needs persistence. Invalid JSON and missing or blank names receive 400 rather than an unexplained server error.

Check the Request Method and URL

Compare the request your client sends with the method your route exports. These are common causes of a mismatch:

  • You opened a POST-only endpoint in the address bar, which sends GET.
  • Your client called fetch(url) without options. That sends GET by default.
  • A form is missing method="post", or its method differs from the handler.
  • The request URL does not match the route file you edited.
  • You are using the App Router but put the handler in the wrong directory, or you are using the Pages Router and expecting App Router behavior.

If you use the Pages Router, API routes are implemented differently: a default handler receives a request and response, and your code checks req.method and sets the response status. Do not copy an App Router export async function POST() handler into a Pages Router API file.

Also verify that the request reaches the Next.js server you are inspecting. A proxy, rewrite, middleware, deployment platform, or different running development server can return its own status. Check the response headers and relevant server logs if the route code does not appear to run.

Send POST Correctly with fetch()

Set the method explicitly, send a JSON body, and identify its content type:

async function createItem() {
  const response = await fetch("/api/items", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name: "Pen" }),
  });

  const data = await response.json();

  if (!response.ok) {
    throw new Error(data.error ?? `Request failed: ${response.status}`);
  }

  return data;
}

fetch() normally resolves to a Response for HTTP errors such as 400 or 405; it does not reject just because the status is outside the 2xx range. Check response.ok or response.status before treating the operation as successful. Network failures can reject the promise, so call this function from code that also handles exceptions. This example assumes the server returns JSON; if an endpoint can return an empty or non-JSON body, handle that response format before calling response.json().

A relative URL such as /api/items works in a browser page served from the same origin. For a request made in a server-side environment, configure the appropriate absolute origin when required by that environment.

Test the Route

Start your Next.js development server, then test both methods from a terminal. The -i option displays the response status and headers as well as the body.

curl -i http://localhost:3000/api/items

Expected: a successful status and a JSON body containing items.

curl -i -X POST http://localhost:3000/api/items \
  -H 'Content-Type: application/json' \
  --data '{"name":"Pen"}'

Expected: status 201 and a JSON body containing the created-style item.

To check the method-mismatch case, send a method the route does not export:

curl -i -X DELETE http://localhost:3000/api/items

Because the example exports GET and POST, it does not implement DELETE. The response should indicate that the method is not allowed. If the status differs, inspect the exact URL, the server handling the request, and any proxy or middleware in front of it.

Read the Response Status When Debugging

In browser developer tools, open the Network panel and inspect the failed request. Confirm the request URL and method, then read the status and response body. This often reveals that the browser sent GET when the code was intended to send POST, or that the request went to a different path.

You can also log the response while debugging:

const response = await fetch("/api/items", { method: "GET" });
console.log(response.status, response.statusText);
console.log(await response.text());

Use response.text() here to inspect the body as-is. A response body can only be consumed once, so do not read it as text and then try to parse the same body as JSON. For a quick look at a returned status code, the TidyURLs HTTP status checker may be useful; still test the actual request method and route behavior with your own client or curl.

Common Mistakes

  • Assuming fetch() sends POST automatically. It sends GET unless you set method.
  • Testing a POST-only endpoint in the address bar. Address-bar navigation uses GET.
  • Confusing the path with the method. A correct /api/items path can still receive a 405 if the method is unsupported.
  • Ignoring the response status. A resolved fetch() promise does not mean the HTTP request succeeded.
  • Sending an invalid body. For JSON, serialize the value with JSON.stringify() and set Content-Type: application/json.
  • Mixing router conventions. App Router Route Handlers and Pages Router API routes use different handler patterns.
  • Expecting a sample handler to persist data. The example returns a response but does not write to storage.

Best Practices

  • Export only the methods your endpoint is designed to support, and document the expected request format for each.
  • Validate request data on the server even if a browser form already checks it.
  • Return meaningful status codes: for example, 400 for invalid input and 201 when a resource is created.
  • Check response.ok or response.status in clients, and handle network errors separately.
  • Test the route with the method and headers your real client will send. Include at least one unsupported-method test when checking a new endpoint.

Conclusion

A Next.js 405 is usually a sign that the request method and the route’s handlers do not match. Verify the URL, export the intended method, configure fetch() explicitly, and inspect the actual response. Testing with both the intended method and an unsupported one makes the mismatch easier to pinpoint.

References