All Next.js 15 App Router dynamic API routes return 404 on Vercel

I’m experiencing an issue where all dynamic API routes in my Next.js App Router project return 404 on Vercel, despite building successfully and appearing in the Functions list.

Environment

  • Next.js version: 15.4.7

  • Router: App Router

  • Production URL: https://paideia-sigma.vercel.app

  • Framework detected: Next.js (auto-detected after removing vercel.json)

  • Local development: Works perfectly with npm run dev

Current behavior: On Vercel, all dynamic API routes (/api/[param]), e.g. /api/quiz/4w4EHpNCtGXv0CDCM52T and /api/test-dynamic/123, return the static 404 page (X-Matched-Path: /404) with no function logs. Static routes like /api/health and /api/test-simple work.
Expected behavior: Dynamic API routes should match their functions (they’re listed in Functions) and return JSON.
The pattern is clear: routes without dynamic segments [param] work fine, but any route with a dynamic segment returns 404.

Steps to reproduce:
Hit https://paideia-sigma.vercel.app/api/health → 200 JSON.
Hit https://paideia-sigma.vercel.app/api/test-dynamic/123 → 404 page, X-Matched-Path: /404, no logs.
Same for /api/quiz/.
Key observations:
Routes work perfectly locally with npm run dev
Routes appear in the Functions list in Vercel dashboard
Build logs show successful compilation
Response headers show X-Matched-Path: /404
No function execution logs are generated when accessing dynamic routes on Vercel
Removed vercel.json - no change
Middleware only matches /admin/*

Why would all dynamic API routes return 404 while static routes work, despite successful builds and the routes appearing in the Functions list? Is this a known issue with Next.js 15.4.7 or is there a configuration problem I’m missing?

and ofc the routes work perfectly locally with npm run dev. Let me know if there are any questions, I vibe coded this so I will try my best to answer. This is also my first deployment so I’m learning as much as I can.

There’s another community post with 404 debugging tips that might be helpful. Please give these solutions a try and let us know how it goes.

A human should be around soon to offer more advice. But you can also get helpful information quickly by asking v0.

In each dynamic API route file:

export const runtime = "nodejs";   // <-- FORCE serverless/node

Redeploy → dynamic routes should immediately start working.

If still broken –> Disable file-system dynamic route prerendering

export const dynamic = "force-dynamic";

Redeploy → test again.

This is expected behavior, [param] will only catch a single segment so it should work for /api/quiz but not /api/quiz/id

/api/[...segments] should be the route pattern you’re looking for

Tried, it didn’t work :frowning:

Yes, but in my app I’m accessing quizzes by ID via /api/quiz/[id]. For example: /api/quiz/4w4EHpNCtGXv0CDCM52T.

I should’ve specified, I’m not trying to match /api/quiz without an ID, only /api/quiz/[id]. My problem is even though there are no segments after id, it still doesn’t work with just /api/quiz/[id] and it’s driving me crazy.

Locally, hitting /api/quiz/4w4EHpNCtGXv0CDCM52T works; on Vercel it returns the static 404 page (X-Matched-Path: /404) with no function logs, even though this route is listed in Functions.

I tried something new:
The App Router handler is a catch‑all at the end of the path: src/app/api/quiz/[…segments]/route.ts with:

export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
export const dynamicParams = true

export async function GET(_req: Request, { params }: { params: { segments?: string | string[] } }) {
  const segments = Array.isArray(params.segments) ? params.segments : params.segments ? [params.segments] : []
  const sessionId = segments[0] ?? ''
  if (!sessionId) return Response.json({ error: 'Session ID is required' }, { status: 400 })

  // …load quiz by sessionId…
  return Response.json({ sessionId })
}

This route is listed in the Functions tab for the deployment as /api/quiz/[…segments].
Locally (npm run dev), /api/quiz/4w4EHpNCtGXv0CDCM52T works.

On Vercel (https://paideia-sigma.vercel.app/api/quiz/4w4EHpNCtGXv0CDCM52T), it returns the static 404 page (X-Matched-Path: /404) and no function logs.

I’m not hitting /api/quiz without an ID; only /api/quiz/{id}. Static APIs like api/health work; any dynamic API (/api/quiz/{id}) 404s the same way.

Given the catch‑all is at the end of the path and the route exists in Functions, why is router resolving /api/quiz/ to /404 on this deployment?? Thank you

Try to debug it with log, put some logs in same route and check that in vercel logs, I hope this will help you to debug the exact issue on live url.

I have no idea what was wrong, but this was a vercel-specific problem. I deployed on google cloud run with 0 issues.

Hey there, @yaokevinl-7218! I saw you mentioned deploying on Google Cloud Run without issues. Just checking in to see if you still need help with the Vercel dynamic API route problem or if you found a solution. Let me know!

Hi Pauline, I never found a solution. I also changed how my stuff worked and I’m no longer using Vercel. Feel free to close the topic.

1 Like