What Are Route Handlers
Route Handlers let you define server-side endpoints in the App Router by creating a route.ts (or route.js) file inside app/api/... that exports named async functions matching HTTP verbs like GET, POST, PUT, PATCH, and DELETE, each receiving a standard Web Request object and returning a Response or NextResponse. Unlike the old pages/api pages, route handlers live alongside the route segments they serve, and a folder cannot export both a page and a route handler for the same path segment at once.
Cricket analogy: Like each fielding position having a dedicated specialist — a slip catcher, a boundary rider — rather than one generic fielder doing every job on the ground.
// app/api/products/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const products = await db.product.findMany();
return NextResponse.json(products);
}
export async function POST(request: Request) {
const body = await request.json();
const product = await db.product.create({ data: body });
return NextResponse.json(product, { status: 201 });
}The Request/Response Web APIs and Dynamic Segments
Because route handlers use the standard Web Request and Response objects, you can call request.nextUrl.searchParams to read query strings, request.json() to parse a POST body, and return NextResponse.json({ data }, { status: 201 }) for structured responses; dynamic segments like app/api/orders/[id]/route.ts receive their params via the second function argument as { params: { id: string } }. This makes route handlers a drop-in replacement for a small REST API without needing Express or a separate backend service.
Cricket analogy: Like reading the exact required run rate off the scoreboard mid-chase rather than guessing it, then updating the board with the new target after every over.
// app/api/orders/[id]/route.ts
import { NextResponse, type NextRequest } from 'next/server';
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const includeItems = request.nextUrl.searchParams.get('includeItems');
const order = await db.order.findUnique({
where: { id: params.id },
include: { items: includeItems === 'true' },
});
if (!order) return NextResponse.json({ error: 'Not found' }, { status: 404 });
return NextResponse.json(order);
}Caching Behavior of Route Handlers
By default, a GET route handler using no dynamic functions (no cookies(), headers(), or request.nextUrl beyond static parts) is treated as static and cached at build time, similar to a statically rendered page; adding export const dynamic = 'force-dynamic' or reading from the incoming request forces it to run fresh on every request. POST, PUT, PATCH, and DELETE handlers are never cached, since they're inherently meant to mutate state rather than return the same cached response repeatedly.
Cricket analogy: Like a printed pre-match scorecard listing the fixed squad list that's reused all day, versus the live ball-by-ball commentary that must be freshly generated for every delivery.
A GET handler that reads cookies() or headers() is automatically opted out of static caching, because those APIs depend on the specific incoming request and can't produce a single reusable response.
Middleware, Headers, and Cookies in Route Handlers
Route handlers can read and set cookies with the cookies() function from next/headers and inspect incoming headers with headers(), both of which automatically opt the route out of static caching since they depend on the specific incoming request. They also run after any matching middleware.ts, so a route handler can rely on middleware having already validated an auth token or rewritten the URL before its own handler function executes.
Cricket analogy: Like the third umpire only stepping in after the on-field umpire has already made an initial call, reviewing that specific decision rather than officiating from scratch.
Using cookies() or headers() inside a route handler is a common way developers accidentally disable caching for an endpoint that was meant to be static. If you need static caching, avoid these APIs and pass required identifiers as URL search params instead.
- Route Handlers live in route.ts files inside app/ and export functions named after HTTP verbs like GET and POST.
- A folder can export a page.tsx or a route.ts for the same segment, but not both.
- Route handlers use the standard Web Request and Response objects rather than a custom framework API.
- Dynamic segments deliver their values through a params argument, e.g. { params: { id: string } }.
- GET handlers with no dynamic APIs are statically cached at build time by default.
- Reading cookies(), headers(), or using force-dynamic opts a route handler out of static caching.
- POST, PUT, PATCH, and DELETE handlers are never cached since they perform mutations.
Practice what you learned
1. What file name and location defines a Route Handler for /api/products?
2. Which objects do Route Handlers use for incoming requests and outgoing responses?
3. What happens to a GET route handler by default if it doesn't call cookies(), headers(), or force-dynamic?
4. Are POST route handlers ever cached by default?
5. In what order do middleware.ts and a route handler's function body run for a matching request?
Was this page helpful?
You May Also Like
Caching in Next.js
A tour of the four caching mechanisms in the Next.js App Router — Request Memoization, the Data Cache, the Full Route Cache, and the Router Cache — and how they interact.
Revalidation Strategies
Understand time-based revalidation (ISR) and on-demand revalidation with revalidatePath and revalidateTag, and how to wire them into Server Actions and Route Handlers.
Server Actions Explained
Learn how Server Actions let you write server-side mutations that are callable directly from forms and Client Components, with progressive enhancement, pending states, and security considerations.
Fetching Data in Server Components
Learn how to fetch data directly inside React Server Components using async/await, and how Next.js handles deduplication, parallelization, and streaming for those requests.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics