afterbuild/ops
ERR-631/Next.js · API
ERR-631
405 Method Not Allowed · Allow: GET

appears when:When a client calls a Next.js API route with an HTTP method that has no matching named export in route.ts (App Router) or no req.method branch (Pages Router)

API route returns 405 Method Not Allowed

Next.js App Router matches HTTP methods to named exports. If POST is not exported, POST returns 405. The fix is one new function — five minutes.

Last updated 17 April 2026 · 5 min read · By Hyder Shah
Direct answer
API route 405 on Next.js App Router is a missing named export. Each HTTP method maps to a function of the same name in route.ts. Export async function GET / POST / PUT / PATCH / DELETE as needed. On Pages Router, branch on req.method. If output: "export" is set in your config, API routes are disabled entirely — remove that line.

Quick fix for API route 405

app/api/tasks/route.ts
typescript
01// app/api/tasks/route.ts — App Router: one named export per HTTP method02import { NextResponse } from "next/server";03 04export async function GET(req: Request) {05  const { searchParams } = new URL(req.url);06  const userId = searchParams.get("userId");07  return NextResponse.json({ tasks: [], userId });08}09 10export async function POST(req: Request) {11  const body = await req.json();12  // create task...13  return NextResponse.json({ ok: true, created: body }, { status: 201 });14}15 16export async function DELETE(req: Request) {17  const { searchParams } = new URL(req.url);18  const id = searchParams.get("id");19  // delete task...20  return new Response(null, { status: 204 });21}22 23// Any method not exported returns 405 automatically24// Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
App Router API route — one named export per method. Anything not exported automatically returns 405

Deeper fixes when the quick fix fails

01 · Pages Router — single handler with method branching

pages/api/tasks.ts
typescript
01// pages/api/tasks.ts — Pages Router explicit method branching02import type { NextApiRequest, NextApiResponse } from "next";03 04export default async function handler(req: NextApiRequest, res: NextApiResponse) {05  if (req.method === "GET") {06    return res.status(200).json({ tasks: [] });07  }08  if (req.method === "POST") {09    return res.status(201).json({ ok: true });10  }11  // explicit 405 for anything else — set Allow header so clients know what's supported12  res.setHeader("Allow", "GET, POST");13  return res.status(405).json({ error: "method_not_allowed" });14}
Pages Router pattern — one default export, explicit method dispatch. Do not mix with App Router route.ts in the same project.

02 · Contract test hits every method

tests/api/tasks.contract.test.ts
typescript
01// tests/api/tasks.contract.test.ts — Vitest contract test02import { describe, test, expect } from "vitest";03 04const BASE = process.env.TEST_BASE_URL ?? "http://localhost:3000";05 06describe("/api/tasks contract", () => {07  test("GET returns 200", async () => {08    const res = await fetch(`${BASE}/api/tasks`);09    expect(res.status).toBe(200);10  });11 12  test("POST returns 201", async () => {13    const res = await fetch(`${BASE}/api/tasks`, {14      method: "POST",15      headers: { "content-type": "application/json" },16      body: JSON.stringify({ title: "test" }),17    });18    expect(res.status).toBe(201);19  });20 21  test("PATCH returns 405 with Allow header", async () => {22    const res = await fetch(`${BASE}/api/tasks`, { method: "PATCH" });23    expect(res.status).toBe(405);24    expect(res.headers.get("allow")).toContain("GET");25  });26});
Run in CI against preview and production — catches missing exports before they reach users

03 · Typed route helper that enforces method coverage

lib/route.ts
typescript
01// lib/route.ts — helper that makes missing methods impossible to ship02type Handler = (req: Request) => Promise<Response>;03type Methods = { GET?: Handler; POST?: Handler; PUT?: Handler; DELETE?: Handler };04 05export function makeRoute(methods: Methods) {06  return methods;07}08 09// app/api/tasks/route.ts10import { makeRoute } from "@/lib/route";11 12export const { GET, POST } = makeRoute({13  GET: async () => Response.json({ tasks: [] }),14  POST: async (req) => {15    const body = await req.json();16    return Response.json({ ok: true, body }, { status: 201 });17  },18});
Typed helper — a missing method becomes a type error rather than a runtime 405

Why AI-built apps hit API route 405

Next.js App Router introduced a new convention for API routes. Instead of a single default handler (as in Pages Router), each HTTP method is a named export: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. When a request arrives, Next.js looks up the method in the module exports. If no match exists, the framework returns a 405 with an Allow header listing the methods that do exist. There is no fallthrough to a default handler. There is no way to catch-all from inside the route file.

AI builders trained on Pages Router examples often write export default async function handler(req, res) inside app/api/tasks/route.ts. The default export is simply ignored. No warning is printed at build time. The first POST /api/tasks returns 405. The same code pasted into pages/api/tasks.ts would work perfectly — but router mixing silently fails.

The second common source is a partial fix. An AI tool adds POST to the route.ts file but the client is sending PUT. The fix is always to match the client's verb to the server's export, and ideally document the supported methods in the route handler so both sides agree.

The third source is build configuration. Setting output: "export" in next.config.ts tells Next.js to generate a fully static site. API routes are not static — they need a server. In static export mode the framework fails at build with a warning, or ships the static HTML and the API path returns 405 (or 404) at runtime. Remove the output line if you need dynamic routes. Finally, middleware that rewrites or returns a response can intercept the request; confirm your matcher does not cover the API path unexpectedly.

API route 405 by AI builder

How often each AI builder ships this error and the pattern that produces it.

AI builder × API route 405
BuilderFrequencyPattern
LovableEvery App Router scaffoldWrites export default handler in route.ts — silently ignored
Bolt.newCommonMixes Pages Router imports inside an App Router project
v0CommonExports only GET when client also needs POST
CursorSometimesLeaves output: 'export' in next.config after scaffolding a static demo
Replit AgentRareMiddleware rewrites /api/* to a path that only exports GET

Related errors we fix

Stop API route 405 recurring in AI-built apps

Still stuck with API route 405?

Emergency triage · $299 · 48h turnaround
We restore service and write the root-cause report.
start the triage →

API route 405 questions

Why does my Next.js API route return 405 Method Not Allowed?+
In the App Router you must export a named function per HTTP method from route.ts. `export async function GET` handles GET, `export async function POST` handles POST, and so on. If you only export GET and the client sends POST, Next.js returns 405 with an Allow header listing the methods you did export. The fix is to add the missing named export. No config change needed.
What is the difference between App Router and Pages Router API handlers?+
Pages Router uses `pages/api/foo.ts` with a single default export handler that reads req.method to branch on HTTP verb. App Router uses `app/api/foo/route.ts` with named exports per method. Mixing the two patterns in one file silently breaks: if you export default alongside named GET/POST exports in App Router, the default is ignored and 405 returns for any unsupported method. Pick one router and stick to it.
Why did my API route start returning 405 after enabling static export?+
`output: 'export'` in next.config.ts builds a fully static site — no serverless runtime. API routes are incompatible with static export and fail with 405 or 404 at runtime. Either remove the output: 'export' line and deploy as a regular Next.js app, or move the API logic to an external service like Cloudflare Workers and call it from the client. You cannot have both static export and dynamic API routes in the same project.
Can middleware cause a 405 on my API route?+
Yes. A middleware that returns NextResponse.rewrite or NextResponse.redirect can intercept the request before it reaches the route handler. If the rewrite points at a path that only exports GET and the incoming request is POST, the 405 comes from the rewritten destination. Check your middleware matcher and any NextResponse.rewrite calls. Also confirm you have not added a 405 response to a custom middleware that checks method explicitly.
How long does an API 405 fix take?+
Five minutes for the typical missing named export. Ten minutes if the issue is a Pages-Router-style default export that needs to be renamed to per-method exports. Thirty minutes if you need to disable static export, or refactor middleware that intercepts the route. Emergency Triage at $299 includes the fix plus a contract test that hits every exported method with its expected payload.
Next step

Ship the fix. Keep the fix.

Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.

About the author

Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.

Sources