How it works
The explanation, with examples you can run.
The shape of a handler
// app/api/orders/[id]/route.tsimport { getSession } from "@/lib/auth" export async function GET(request: Request, ctx: RouteContext<"/api/orders/[id]">) { const session = await getSession(request) if (!session) return new Response("Unauthorized", { status: 401 }) const { id } = await ctx.params const order = await db.order.findFirst({ where: { id, userId: session.userId } }) if (!order) return new Response("Not found", { status: 404 }) return Response.json({ id: order.id, status: order.status, total: order.total })} export async function DELETE(request: Request, ctx: RouteContext<"/api/orders/[id]">) { // same checks, then the mutation return new Response(null, { status: 204 })}params is a promise, as in pages. A folder can contain a page or a route handler, not both, which is why APIs usually live under app/api/. Route Handlers don't take part in layouts or client navigation; they are plain endpoints.
Caching
Only GET handlers can be prerendered or cached; other methods always run per request. With Cache Components, GET follows the same rules as a page:
- No request access and no uncached I/O: prerendered at build time and served as a static file.
- Reads the request (
request.url, headers, cookies, body), callscookies(),headers()orconnection(), makes an uncached fetch, queries a database, or callsMath.random(): prerendering stops there, and the handler runs per request. - Uncached data you are happy to share can be wrapped in a
use cachefunction, and its result becomes part of the static response.
In apps on the previous model, handlers are not cached by default and a GET opts in with export const dynamic = "force-static".
Handler or Server Action?
Both run on the server and both are public. For mutations triggered by your own React UI, Server Actions are usually simpler: they are called like functions, type-checked, and return a refreshed UI in the same response. Reach for a Route Handler when the caller is not your UI (a payment provider's webhook, a mobile app, a partner), when you need a specific URL, method, status code or content type, or when other services rely on an HTTP contract that must stay stable. Route Handlers can call revalidateTag and revalidatePath, which makes them the usual receiver for "content changed" webhooks.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.