How to debug Next.js API routes and Route Handlers

Updated 28 October 2026 · 6 min read

A Route Handler (app/api/.../route.ts) or a Pages Router API route is server code with a URL. Debugging it means checking three things: what arrived, what it called, and what it returned.

Call the route directly

Take the UI out of the picture first. If the route fails on its own, the bug is on the server:

curl -i "http://localhost:3000/api/tasks?userId=2"

curl -i -X POST http://localhost:3000/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title":"Write the report"}'

-i prints the status and headers, so you can see a 307 redirect or a wrong content type straight away.

Check what arrived

Most Route Handler bugs are about the input. Log it at the top of the handler:

export async function GET(request: NextRequest) {
  console.log(request.method, request.nextUrl.pathname, Object.fromEntries(request.nextUrl.searchParams));
  // ...
}

Pause inside it

Start next dev with NODE_OPTIONS='--inspect', attach Chrome (chrome://inspect) or VS Code, and set a breakpoint in route.ts. With qaisdebugs you click the line in its Sources view instead, and the request, its logs and its outgoing calls are already on screen.

The classic mistakes

Reading the wrong query parameter

The page sends ?userId=2 but the handler reads another name, so the value is null and the API call goes out as ?userId=null. No error is thrown; you just get empty data:

const userId = request.nextUrl.searchParams.get('user');   // the page sends userId → null
const userId = request.nextUrl.searchParams.get('userId'); // correct

This is exactly the kind of bug that is easy to see when the browser's request and the server's outgoing request are side by side.

Forgetting to await params

In dynamic routes such as app/api/users/[id]/route.ts, the second argument's params is a Promise since Next.js 15: use const { id } = await params.

Reading the body twice

request.json() and request.text() consume the body. A second read throws. Read it once and keep the result.

Swallowing errors

A try/catch that returns { ok: false } with status 200 hides the real failure. Log the error and return a real status code.

An upstream error passed on as success

Check response.ok before response.json() when your route calls another API, or a 404 from upstream becomes a confusing empty list.

Pages Router API routes

Routes in pages/api/ debug the same way: they are Node.js handlers with req and res, logs go to the terminal, and breakpoints work with --inspect.