Next.js path · The server side · Topic 7 of 11

Route Handlers: HTTP endpoints in the app folder

Not started

A Route Handler is a route.ts (or .js) file in the app/ tree that answers HTTP requests for its URL directly, with no React rendering. You export one async function per HTTP method; each receives a Web Request (or NextRequest, which adds parsed cookies and URL helpers) and returns a Web Response. They are the App Router's version of API routes, and the natural place for webhooks, OAuth callbacks, RSS and sitemap files, downloads, and JSON APIs used by clients other than your own pages.

Why it matters: Webhooks, feeds, files, and APIs for mobile apps or other services don't want a React page. Route Handlers are where Next.js stops being a UI framework and becomes a small backend, with the caching rules of one.

  1. 1Web Request in, Response out

    Export `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` or `OPTIONS` from `route.ts`. Each receives a standard `Request` and returns a `Response`; other methods get 405.

  2. 2A route or a page, not both

    A segment can have `page.tsx` or `route.ts`, never both. Put APIs under a folder such as `app/api/` to keep them apart.

  3. 3GET follows the page model

    With Cache Components, a GET handler that doesn't touch the request or uncached data is prerendered at build time. Reading headers, the URL or a database makes it run per request unless you `use cache` the data.

  4. 4Public, like everything on the server

    Any client can call a Route Handler. Authenticate, validate and rate-limit as you would any API.

1

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), calls cookies(), headers() or connection(), makes an uncached fetch, queries a database, or calls Math.random(): prerendering stops there, and the handler runs per request.
  • Uncached data you are happy to share can be wrapped in a use cache function, 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.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Your React client components need to save a form. Should you write a `POST` Route Handler or a Server Action?
  2. 2.`export async function GET() { return Response.json({ version: "1.4" }) }`. How is it served after `next build`?
  3. 3.A GET handler reads `request.headers.get('user-agent')`. What happens to prerendering?

0 of 3 right so far. Get all three to finish this topic.