Lesson 17 of 81
Not every response your app sends is a page. A webhook from Stripe, a health check a monitoring tool polls, a JSON endpoint a mobile client calls — none of those want HTML, and none of them are triggered by a form in your own UI. Route Handlers are the App Router's answer: a file that is a real HTTP endpoint, nothing more.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A Route Handler's `request` parameter is typed as `NextRequest`. What is `NextRequest`?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A Route Handler is a route.ts file that exports one plain async function per HTTP method it wants to support — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS — instead of a default-exported component. Each function receives the Web-standard Request object and returns a Web-standard Response (or Response.json(...)), the same objects fetch itself uses in the browser. There's no framework-specific request/response wrapper to learn for the basics.
This is a real departure from the older Pages Router's API routes (pages/api/*.ts), which exported a single default function handler(req, res) with a Node.js-style signature — res.status(200).json(...), res.setHeader(...). If you're pattern-matching against that older API, don't: a Route Handler's request parameter is a NextRequest (an extension of the standard Request, adding convenience like request.nextUrl and request.cookies), and you build a response by constructing or returning a Response/NextResponse, not by calling methods on a mutable res object.
// app/api/webhooks/stripe/route.ts
import type { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
const signature = request.headers.get("stripe-signature");
const body = await request.text();
if (!signature || !isValidStripeSignature(body, signature)) {
return new Response("Invalid signature", { status: 400 });
}
const event = JSON.parse(body);
await handleStripeEvent(event);
return Response.json({ received: true });
}
This route exists specifically because Stripe's servers need a stable URL they can POST to directly — there's no React tree involved, no form submission from a signed-in user, just a third-party service making a raw HTTP call. That's the test for reaching for a Route Handler over a Server Action: is the caller something other than your own app's UI? Webhooks, a public JSON API for other clients, a non-React front end consuming your backend, an endpoint a CLI or mobile app hits directly — all of those need a real, addressable HTTP endpoint with proper method routing, which is exactly what route.ts gives you. A form-driven mutation triggered from your own page (submitting a comment, updating a profile) is better served by a Server Action, which skips hand-rolling the fetch call, request parsing, and response shape entirely.
request is a NextRequest, not a Node.js IncomingMessage. There's no req.body to read directly — you call await request.json(), await request.text(), or await request.formData(), the same methods a browser's fetch Response exposes.params in a Route Handler is a Promise, same as in a page. { params }: { params: Promise<{ id: string }> }, and you await params before reading values off it — this didn't change independently for Route Handlers, it's the same async-params behavior the rest of the App Router uses.OPTIONS is handled for you automatically if you don't define it, with an appropriate Allow header based on whichever methods you did export — you don't need to hand-write CORS preflight handling just to stop it from 405ing."When would you reach for a Route Handler instead of a Server Action?" tests whether a candidate has a real mental model for the App Router's two mutation surfaces, or is just reaching for whichever one they used most recently — the right answer hinges on who's calling it, not on personal preference.