Module F-7·21 min read

Building GET/POST/PUT/DELETE handlers with NextRequest and NextResponse, the new proxy.js convention, userAgent() for device detection, and when to use a Route Handler vs a Server Action.

F-7 — Route Handlers and the Backend-for-Frontend Pattern

Who this is for: Developers who understand Server Components and data fetching from F-3 and F-4, and need to build API endpoints within a Next.js application — for third-party webhooks, mobile clients, public APIs, or proxying external services. This module also covers the proxy.js convention introduced in Next.js 15 and clarifies the often-confused question of when to use a Route Handler versus a Server Action.


When You Actually Need a Route Handler

Most data operations in a modern Next.js application don't need a Route Handler. Server Components fetch data directly. Server Actions handle mutations. Between those two primitives, the majority of the client-server communication in your app can happen without you ever writing an HTTP endpoint.

But some situations genuinely require one:

Third-party webhooks. Stripe, GitHub, Shopify, and every other service that sends webhook events needs a publicly accessible HTTP endpoint to POST to. Server Actions can't receive requests from external systems. Route Handlers can.

Public APIs for non-browser clients. If you have a mobile app, a CLI tool, or any consumer that isn't your Next.js frontend, you need a real HTTP API. Route Handlers are that API.

OAuth and auth callbacks. Authentication flows that redirect back to your app with a code or token need a dedicated endpoint. NextAuth handles this internally with Route Handlers under the hood.

Response streaming. Server-Sent Events (SSE), large file downloads, or any response where you need control over the HTTP response body as a stream.

Custom headers or response formats. If a client needs a response in a specific format (CSV export, XML feed, binary data), Route Handlers give you direct control over the Response object.

For everything else — mutations triggered by user interaction in your own frontend, data loading for your own pages — Server Actions and Server Components are the right tools.


The Basics

A route.ts file in any app/ directory folder creates an HTTP endpoint. Export a function named after the HTTP verb:

ts

Any HTTP verb not exported returns 405 Method Not Allowed automatically. Supported verbs: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.


Dynamic Route Handlers

Route Handlers work with the same dynamic segment syntax as pages:

ts

params in Route Handlers is also a Promise in Next.js 15 — await it, just like in pages.


Reading the Request

NextRequest extends the standard Web API Request with Next.js-specific utilities:

ts

Building Responses

Use NextResponse for most cases, or the standard Web API Response when you need more control:

ts

Caching Behaviour

GET Route Handlers are cached by default when they return a static response (no dynamic data access). They become dynamic — and bypass the cache — when they:

  • Read from the incoming Request object (headers, cookies, body)
  • Use dynamic functions like cookies() or headers()
  • Use export const dynamic = 'force-dynamic'
ts

Force a Route Handler to always revalidate after a period:

ts

Webhook Handlers — The Real-World Pattern

Webhooks from Stripe, GitHub, or any other service have two requirements: they need to be accessible from the outside (no auth cookie, no session — these are server-to-server requests), and they often need signature verification to ensure the request actually came from the service you expect.

ts

Two things to notice:

Raw body for signature verification. await request.text() gives you the raw request body as a string. This is essential for webhook signature verification — Stripe (and most other services) sign the raw body bytes. If you parse it as JSON first, the bytes change and signature verification fails.

Return 200 quickly. Webhook providers typically require a response within 30 seconds and will retry on timeout or error status. Do your heavy processing asynchronously (queue it, use after() from Next.js 15, or fire a background job) and respond immediately with { received: true }.


Server-Sent Events — Streaming from Route Handlers

Route Handlers can stream responses, which is how you implement Server-Sent Events (SSE) for real-time updates:

ts
tsx

A note on serverless and SSE: long-lived connections like SSE are problematic in serverless environments (Vercel Functions, AWS Lambda) because functions terminate after a response. Vercel's streaming support has improved, but for sustained real-time connections, consider Pusher, Ably, or Upstash for server-less-friendly pub/sub — or self-host on a platform that supports persistent connections. Module A-14 covers this in depth.


The proxy.js Convention — Declarative API Proxying (Next.js 15)

Next.js 15 introduced a proxy.js (or proxy.ts) file convention as a declarative alternative to writing proxy Route Handlers manually.

Place a proxy.js file in any route segment to proxy requests for that path to an external service:

ts

With this file in place:

  • GET /api/github/users/octocatGET https://api.github.com/users/octocat
  • GET /api/github/repos/vercel/next.jsGET https://api.github.com/repos/vercel/next.js

You can add headers to outgoing requests (like auth tokens that shouldn't be exposed to the browser):

ts

This pattern is the BFF (Backend-for-Frontend) pattern made declarative. Your browser calls your Next.js API. Next.js adds the secret API key and forwards to the real service. The API key never reaches the browser.

Before proxy.js, you'd write this manually:

ts

proxy.js eliminates this boilerplate for the straightforward case.


userAgent() — Device Detection

Route Handlers can use userAgent() from next/server to detect the requesting device type — useful for serving different content, redirecting mobile users to a different experience, or A/B testing:

ts

userAgent is more commonly used in Middleware (which you'll cover in P-5) since Middleware runs before every request. In Route Handlers, it's useful when you need device-aware API responses.


CORS Configuration

If your Route Handlers will be called from a different origin (a mobile app, a third-party frontend, Postman during development), you need CORS headers:

ts
ts

The OPTIONS export handles the CORS preflight request. Every other method needs the CORS headers in its response too.


Route Handler vs Server Action — The Decision

The question comes up constantly and the answer is clean once you understand what each tool is for:

SituationUse
User submits a form in your own Next.js frontendServer Action
User clicks a button that mutates dataServer Action
External service sends a webhookRoute Handler
Mobile/native app needs an API endpointRoute Handler
You need to stream a responseRoute Handler
You need to return a non-JSON format (CSV, XML)Route Handler
You need full control over HTTP status/headersRoute Handler
You're building a BFF proxy for an external APIRoute Handler (proxy.js)
You want to call a mutation from a Client ComponentServer Action (no explicit fetch needed)

The rule: Server Actions for your own frontend. Route Handlers for everything else.

Server Actions don't require you to write fetch calls or define endpoint URLs — they're called like functions. Route Handlers require explicit HTTP requests — the caller constructs a URL and makes a fetch call. For internal mutations in your own app, Server Actions win on simplicity. For external consumers, you need the explicit HTTP contract of a Route Handler.


Where We Go From Here

F-8 is the capstone module for the Foundation phase — you'll build a complete content site from scratch using everything covered in F-1 through F-7: the file system, Server Components, data fetching, dynamic routes, built-in components, and Route Handlers. Think of it as a supervised first real application.

After F-8, the Practitioner phase begins. P-1 goes deep on advanced data fetching patterns — the use cache directive, tag-based invalidation at scale, and eliminating the waterfall chains that make apps feel slow. P-2 covers Server Actions properly — not just how to write them but how they're compiled, the security implications, useActionState, optimistic UI, and the after() API.


Knowledge Check

When configuring Server-Sent Events (SSE) in a Route Handler, what specific headers must be returned in the Response?


What is the primary purpose of the proxy.js (or proxy.ts) file convention introduced in Next.js 15?


According to the module, what is the core rule for deciding between a Server Action and a Route Handler?

Test your knowledge with more question sets

Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.