Next.js Route Handlers
Build custom HTTP endpoints in the App Router using Web Request and Response APIs
Overview
Route Handlers define API endpoints inside the App Router using a route.ts file. They export functions named after HTTP methods (GET, POST, etc.) and use the standard Web Request and Response objects. Use them for webhooks, third-party integrations, or JSON APIs consumed by clients.
Syntax / Usage
Create app/api/<name>/route.ts and export one function per HTTP method. Handlers receive a Request and can read params from the second argument.
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const query = request.nextUrl.searchParams.get('q')
const posts = await db.post.findMany({ where: { title: { contains: query } } })
return NextResponse.json(posts)
}
export async function POST(request: NextRequest) {
const body = await request.json()
const post = await db.post.create({ data: body })
return NextResponse.json(post, { status: 201 })
}
Examples
Read a dynamic segment via the typed params promise:
// app/api/posts/[id]/route.ts
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const post = await db.post.findUnique({ where: { id } })
if (!post) return new Response('Not found', { status: 404 })
return Response.json(post)
}
Control caching — handlers are dynamic by default; opt into static caching:
// app/api/config/route.ts
export const dynamic = 'force-static'
export async function GET() {
return Response.json({ theme: 'dark' })
}
Common Mistakes
- Defining a Route Handler and a
page.tsxin the same segment folder (conflict) - Forgetting to
await request.json()before reading the body - Assuming
GEThandlers are always cached — they are dynamic when reading request data - Returning a plain object instead of a
Response/NextResponse - Not awaiting the
paramspromise in newer App Router versions
See Also
nextjs-server-actions api-routes nextjs-caching