Next.js app/api 404 vs 405: route.js and named exports
Reproduced on Next.js 16.4.2: why app/api/admin/all.js answers 404, what a 405 means instead, and the static export case that only 404s in production.
TL;DR#
- Cause: in the App Router, a URL only exists when its folder contains a
page.jsor aroute.js. A file calledapp/api/admin/all.jsis neither, soGET /api/admin/allhas no handler and Next.js answers 404; in the reproduction the dev server printed nothing but the 404 line. - Fix: move the code to
app/api/admin/all/route.js(the folder is the URL, the file name is fixed) and replace the Pages Router(req, res)default export with a namedGETfunction that returns aResponse. - If you now get 405 instead of 404, the file is found but exports no usable method: a default export or a lowercase
getboth do that. - Reproduced on Next.js 16.4.2 with Node v24.21.0 in the official
node:24-alpineimage, 19 October 2026.
The error#
The Stack Overflow question comes from a Next.js 13 project that put its API files under app/api/admin/ as plain files, written in the Pages Router style: export default async function all(req, res) with a res.status(200).json(...) reply. Every call, from Postman, from curl or from the app itself, came back 404, and renaming files did not help.
The same layout on Next.js 16.4.2 gives the same result. With only app/api/admin/all.js in place, the dev server logs a plain 404 and nothing else:
GET /api/admin/all 404 in 681ms (next.js: 578ms, application-code: 103ms)The full dev log of that run holds only the start-up banner, the home page request and this line: no compile error, no hint. The router never matches the file, so the code inside it never runs and cannot fail.
Why it happens#
The App Router builds URLs from folders, and only two file names make a folder reachable. The project structure documentation says it directly: "a route is not publicly accessible until a page.js or route.js file is added to a route segment." Any other file in app/ is treated as colocated code and is never served.
So in app/api/admin/all.js, the router sees a folder app/api/admin with no route.js in it, and a file it ignores. The URL /api/admin/all matches nothing.
The second half of the problem is the function signature. (req, res) with res.status().json() belongs to the Pages Router's API Routes. The Route Handlers guide describes Route Handlers as "the equivalent of API Routes inside the pages directory", built on the Web Request and Response APIs, and the route.js reference lists the supported exports: one named async function per HTTP method, GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS. Moving the old file to the right place without changing the signature is not enough, as the 405 section below shows.
Fix#
1. Turn the file into a folder with a route.js#
The URL path is the folder path. To keep /api/admin/all, the handler lives in a folder called all:
app/
api/
admin/
all/
route.js2. Export a named GET handler#
Replace the default export with a function named after the HTTP method. The method check disappears: Next.js handles that itself (step 3).
// app/api/admin/all/route.js
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export async function GET(request) {
try {
const admins = await prisma.admin.findMany()
return Response.json(admins)
} catch (error) {
return Response.json({ error: 'Failed to get admins' }, { status: 500 })
}
}Response.json() is the Web API the route.js reference uses in its own examples; the accepted answer uses NextResponse.json() from next/server instead, which, per the NextResponse reference, extends the Web Response API with convenience methods.
3. Drop the manual 405 branch#
The old if (req.method !== 'GET') block has no place to go and is not needed. The Route Handlers guide states: "If an unsupported method is called, Next.js will return a 405 Method Not Allowed response." In the reproduction, a route exporting only POST answered a GET with 405 and the POST with 200:
GET /api/admin/all 405 in 155ms (next.js: 96ms, application-code: 59ms)
POST /api/admin/all 200 in 18ms (next.js: 1.6ms, application-code: 16ms)Alternative: keep the old code in pages/api#
If the project still has, or can have, a pages directory, the original (req, res) function works unchanged at pages/api/admin/all.js. Reproduced on 16.4.2 with that exact file next to an app directory:
GET /api/admin/all 200 in 58ms (next.js: 39ms, application-code: 19ms)
POST /api/admin/all 405 in 11ms (next.js: 1.2ms, application-code: 10ms)Here the 405 is the file's own res.status(405) branch. Pick one router per endpoint; the Route Handlers guide notes that you do not need both for the same job.
A note on the accepted answer#
The accepted answer is right about the cause, but copying its code changes two things. It puts the file at app/api/admin/route.js, which serves /api/admin, not /api/admin/all: in the reproduction, with a GET handler at that path, /api/admin returned 200 and /api/admin/all still returned 404. And its example returns 405 from GET and runs the query in POST, so the asker's original GET request would get a 405. Keep the folder name that matches the URL you call, and put the query in the method you send.
Verify the fix#
Start the dev server (every run in the reproduction started a fresh one), call the route and look at the status code:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/api/admin/allIt prints 200, and the dev server logs the hit:
GET /api/admin/all 200 in 123ms (next.js: 89ms, application-code: 34ms)Drop -o /dev/null to see the JSON body. If the code is still 404, the file is still not where the router looks (next section); if it is 405, the file is found but its exports are wrong (the section after).
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
When the 404 survives a correct route.js#
Three more layouts produced a 404 in the reproduction, each with a valid handler inside:
- The file is called
routes.js, or anything else close.app/api/admin/all/routes.jswith a correctGETreturned 404. The name is exactlyroute, with a.jsor.tsextension. - The project has both
app/andsrc/app/. With a rootapp/containing only the home page and the handler undersrc/app/api/admin/all/route.js, the request returned 404. Moving everything undersrc/appand deleting the rootapp/made the same handler return 200. Thesrcfolder documentation says why: "src/apporsrc/pageswill be ignored ifapporpagesare present in the root directory." - The URL you call does not match the folders. The
/api/adminversus/api/admin/allcase above is the common one.
The Route Handlers guide adds one more rule: "there cannot be a route.js file at the same route segment level as page.js". A page and an API endpoint need different URLs. Folders prefixed with an underscore are also out: per the project structure documentation, a private folder opts "the folder and all its subfolders" out of routing, so a handler under app/_api/ is not served.
405 instead of 404: the file is found, the export is not#
Once the file is in the right place, a wrong export changes the status from 404 to 405, and the dev server finally explains itself. A route.js that keeps the old default export:
⨯ Detected default export in '[project]/app/api/admin/all/route.js'. Export a named export for each HTTP method instead.
⨯ No HTTP methods exported in '[project]/app/api/admin/all/route.js'. Export a named export for each HTTP method.
GET /api/admin/all 405 in 115ms (next.js: 71ms, application-code: 44ms)And one that exports a lowercase get:
⨯ Detected lowercase method 'get' in '[project]/app/api/admin/all/route.js'. Export the uppercase 'GET' method name to fix this error.
⨯ No HTTP methods exported in '[project]/app/api/admin/all/route.js'. Export a named export for each HTTP method.
GET /api/admin/all 405 in 197ms (next.js: 152ms, application-code: 45ms)These messages were observed with next dev on 16.4.2; the 404 or 405 status itself is the signal to read. A 404 means look at the file's path and name, a 405 means look at what it exports. The same export rule is behind a build-time error, on the pre-16 releases that page tested (its test found the 16 line accepted the same file), when a route file exports anything other than handlers and segment config, covered in the authOptions is not a valid Route export field fix.
Works locally, 404 after deploy: output export#
One answer under the question reports routes that work on localhost and 404 once deployed, fixed by removing output: 'export' from next.config.js. The reproduction shows why, and refines it. With a static export, next build writes files to out/ and no server runs afterwards. A route.js that only exports POST built with exit code 0, and out/ contained no api folder at all:
$ ls out/api 2>&1
ls: out/api: No such file or directoryA static host serving that folder has nothing to return for /api/admin/all, hence the production-only 404. A GET handler without extra configuration failed the build instead:
Error: export const dynamic = "force-static"/export const revalidate not configured on route "/api/admin/all" with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-exportAdding export const dynamic = 'force-static' to that GET route made the build write out/api/admin/all containing the JSON. That matches the static exports guide: "Only the GET HTTP verb is supported", and handlers "that rely on Request" are listed as unsupported. If the endpoint must read the request or accept a POST, it needs a server: remove output: 'export' and deploy with next start or a platform that runs Next.js. If the static export is the point, the Firebase Hosting page on 404s after a static export covers the hosting side, where the files exist but the host does not map clean URLs to them.
The next error: calling the route from a Server Component#
With the 404 gone, the next stumble is often calling the route from a Server Component with a relative URL. On 16.4.2, a page doing await fetch('/api/admin/all') on the server failed with:
⨯ TypeError: Failed to parse URL from /api/admin/allServer code has no page address to resolve /api/... against. The only absolute URLs are supported fix explains this message and its older wording, and the better fix: call the Prisma query (or the function behind the route) directly from the Server Component instead of fetching your own API over HTTP. If the route is called from a different origin in the browser instead, the failure to expect is a CORS rejection, covered in the blocked by CORS policy from origin localhost:3000 fix.
The whole problem comes down to two rules: the folder path is the URL and the file must be called route.js, and the file exports one uppercase function per method. A 404 says the first rule is broken, a 405 says the second one is, and a 404 that only appears after deployment points at output: 'export' in the config.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Next.js favicon not working: app/favicon.ico vs public/
Reproduced on Next.js 16.4.2: with an icon in both app/ and public/, next dev and next start served different files without a warning. Where each icon file must live, and what renaming to icon.ico really changes.
Next.js: Error: only absolute urls are supported (fix)
Why fetch("/api/products") works after a client-side click but throws on refresh, what each fetch implementation prints, and the two fixes that hold in both routers.
Fix Error: NEXT_REDIRECT logged in a Route Handler catch
Reproduced on Next.js 16.4.2: why redirect() inside try/catch logs Error: NEXT_REDIRECT, sends users to the wrong URL or returns a 500, and the three fixes that hold.


