Next.js

Next.js Route Handlers👨‍💻

Route Handlers are how you build API endpoints in Next.js. You create a route.ts file, export functions named after HTTP methods (GET, POST, PUT, DELETE), and Next.js maps them to URLs based on the file's location in app/. Under the hood, they use the Web Platform's Request and Response APIs — the same ones that power Service Workers and Cloudflare Workers — so everything you learn transfers outside Next.js.

The mental model is straightforward: route.ts is to APIs what page.tsx is to UI. They cannot coexist in the same directory (pick one per folder), and they run exclusively on the server. This means you can safely access databases, environment variables, and secrets without anything leaking to the browser.

Where things get interesting is the caching behavior. A GET handler with no dynamic input gets statically evaluated at build time — effectively becoming a JSON file on the CDN. The moment you read cookies, headers, or search params, Next.js switches to dynamic rendering. POST, PUT, and DELETE are always dynamic. Understanding this split is the key to building fast APIs that stay fresh when they need to.

Key Takeaways

  • 1Route Handlers are defined in `route.ts` files inside `app/`. The folder path becomes the URL — `app/api/users/route.ts` serves `/api/users`
  • 2Export named functions matching HTTP methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`. Names must be uppercase — `get()` silently does nothing
  • 3Dynamic segments use bracket folders: `app/api/users/[id]/route.ts`. Params are accessed via the second argument and must be awaited: `const { id } = await props.params`
  • 4GET handlers are cached by default (static evaluation at build time). Reading cookies, headers, or search params opts into dynamic rendering. POST/PUT/DELETE are always dynamic
  • 5Use `Response.json()` for JSON responses with proper Content-Type. Use `new Response()` when you need full control over headers, status, or streaming
  • 6Route Handlers are the right choice when you need an endpoint consumed by external clients, webhooks, or non-React frontends. For internal form mutations triggered from your own UI, prefer Server Actions

Master next.js route handlers

Take the Next.js Full-Stack course with hands-on lessons and challenges.

Examples

REST CRUD — collection and item endpoints

typescript

Two files handle the full CRUD lifecycle. The collection route (app/api/posts/route.ts) handles listing with pagination and creation. The item route (app/api/posts/[id]/route.ts) handles fetching, updating, and deleting a single resource. Note that params are a Promise that must be awaited — this changed in Next.js 15.

Dynamic route with Zod validation

typescript

Always validate both URL params and request body. Params are strings by default — a bare [id] could be anything. Zod's safeParse avoids uncaught exceptions and gives you structured errors to return. The refine on UpdateUserSchema prevents empty PATCH requests that would hit the database for nothing.

Webhook handler with signature verification

typescript

Webhook endpoints must verify signatures to prevent forged requests. Read the body as text (not JSON) because signature verification needs the raw payload. The idempotency check prevents duplicate processing when Stripe retries delivery. Always return 200 quickly — if processing takes too long, Stripe assumes the webhook failed and retries.

Streaming response — Server-Sent Events

typescript

ReadableStream lets you send data incrementally. The SSE format requires 'data: ...' lines separated by double newlines. The 'event:' field lets clients listen for specific event types. Set Cache-Control to no-cache — you never want a CDN caching a live event stream. Mark the route as force-dynamic so Next.js does not try to statically render it.

File upload endpoint

typescript

Use request.formData() to parse multipart uploads. Always validate file type and size before processing — never trust the client. The file is a Web API File object, so call arrayBuffer() to get the raw bytes. In production, upload to cloud storage (S3, R2, Vercel Blob) rather than writing to the local filesystem, which does not persist on serverless deployments.

Authenticated endpoint with JWT check

typescript

Extract the JWT verification logic into a shared utility so every protected route stays clean. Return 401 for missing or invalid tokens and 403 for insufficient permissions — they mean different things and clients handle them differently. Use jose (Edge-compatible) over jsonwebtoken (Node-only) if your Route Handler might run on the Edge runtime.

Common Mistakes

Mistake:

Placing `route.ts` and `page.tsx` in the same directory — Next.js throws a build error because it cannot serve both a page and an API from the same path

Fix:

Put API routes in a separate folder. Use `app/api/users/route.ts` for the API and `app/users/page.tsx` for the UI, or nest the API under a different segment like `app/dashboard/api/route.ts`.

Mistake:

Exporting lowercase method names like `get()` or `post()` — they are silently ignored and the route returns 405 Method Not Allowed

Fix:

Method exports must be uppercase: `export async function GET()`, `export async function POST()`. This matches the HTTP specification and is how Next.js identifies which methods the route supports.

Mistake:

Assuming GET handlers are always dynamic — a GET handler that does not read cookies, headers, or search params gets statically evaluated at build time and cached indefinitely

Fix:

If your GET handler returns data that changes (e.g., database queries), export `const dynamic = 'force-dynamic'` or `const revalidate = 60` to control caching. Otherwise the response is frozen at build time.

Mistake:

Reading `request.json()` twice in the same handler — the body is a stream that can only be consumed once. The second call returns an error or empty result

Fix:

Store the parsed body in a variable: `const body = await request.json()`. Then reference `body` wherever you need the data. If you need both text and JSON, read as text first and then `JSON.parse()` it.

Best Practices

  • Validate everything at the boundary. Use Zod to parse both URL params and request bodies before they reach your business logic. A route handler is your API's front door — reject bad input early with structured error messages.
  • Use Route Handlers for external consumers (mobile apps, third-party integrations, webhooks) and Server Actions for internal mutations triggered by your own React UI. Mixing them up adds unnecessary network hops or loses the progressive enhancement that Server Actions provide.
  • Return appropriate HTTP status codes: 201 for resource creation, 204 for successful deletion, 422 for validation errors, 409 for conflicts. Frontend code and API consumers rely on status codes for control flow — returning 200 for everything forces them to parse error bodies.
  • Set explicit caching headers on public GET endpoints. Use `Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400` to let CDNs serve cached responses while revalidating in the background. This one header can drop your serverless costs dramatically.
  • Keep webhook handlers idempotent. Store the event ID before processing and check for duplicates at the start. Webhook providers retry aggressively — without idempotency, a single Stripe payment can create duplicate orders.

Summary

Route Handlers are Next.js's mechanism for building server-side API endpoints using standard Web APIs. Create a `route.ts` file anywhere in `app/`, export uppercase HTTP method functions, and the folder path becomes your URL. GET handlers are cached by default unless they read dynamic inputs like cookies or search params — POST, PUT, and DELETE are always dynamic. Use them for external API consumers, webhooks, and file uploads. For internal form mutations in your own React components, prefer Server Actions instead. Validate inputs with Zod, return proper status codes, set cache headers on public endpoints, and always verify webhook signatures.

Practice Next.js with hands-on challenges

Learn next.js route handlers hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master Next.js with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.