How to debug Next.js API routes and Route Handlers
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.