Route Handlers
Route Handlers
Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.

Good to know: Route Handlers are only available inside the
appdirectory. They are the equivalent of API Routes inside thepagesdirectory meaning you do not need to use API Routes and Route Handlers together.
Convention
Route Handlers are defined in a route.js|ts file inside the app directory:
export async function GET(request: Request) {}Route Handlers can be nested anywhere inside the app directory, similar to page.js and layout.js. But there cannot be a route.js file at the same route segment level as page.js.
Supported HTTP Methods
The following HTTP methods are supported: GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS. If an unsupported method is called, Next.js will return a 405 Method Not Allowed response.
Extended NextRequest and NextResponse APIs
In addition to supporting the native Request and Response APIs, Next.js extends them with NextRequest and NextResponse to provide convenient helpers for advanced use cases.
Caching
Route Handlers are not cached by default. You can, however, opt into caching for GET methods. Other supported HTTP methods are not cached. To cache a GET method, use a route config option such as export const dynamic = 'force-static' in your Route Handler file.
export const dynamic = 'force-static' export async function GET() { const res = await fetch('https://data.mongodb-api.com/...', { headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY, }, }) const data = await res.json() return Response.json({ data }) }Good to know: Other supported HTTP methods are not cached, even if they are placed alongside a
GETmethod that is cached, in the same file.
With Cache Components
When Cache Components is enabled, GET Route Handlers follow the same model as normal UI routes in your application. They run at request time by default, can be prerendered when they don't access dynamic or runtime data, and you can use use cache to include dynamic data in the static response.
Static example - doesn't access dynamic or runtime data, so it will be prerendered at build time:
export async function GET() { return Response.json({ projectName: 'Next.js', }) }Dynamic example - accesses non-deterministic operations. During the build, prerendering stops when Math.random() is called, deferring to request-time rendering:
export async function GET() { return Response.json({ randomNumber: Math.random(), }) }Runtime data example - accesses request-specific data. Prerendering terminates when runtime APIs like headers() are called:
import { headers } from 'next/headers' export async function GET() { const headersList = await headers() const userAgent = headersList.get('user-agent') return Response.json({ userAgent }) }Good to know: Prerendering stops if the
GEThandler accesses network requests, database queries, async file system operations, request object properties (likereq.url,request.headers,request.cookies,request.body), runtime APIs likecookies(),headers(),connection(), or non-deterministic operations.
Cached example - accesses dynamic data (database query) but caches it with use cache, allowing it to be included in the prerendered response:
import { cacheLife } from 'next/cache' export async function GET() { const products = await getProducts() return Response.json(products) } async function getProducts() { 'use cache' cacheLife('hours') return await db.query('SELECT * FROM products') }Good to know:
use cachecannot be used directly inside a Route Handler body; extract it to a helper function. Cached responses revalidate according tocacheLifewhen a new request arrives.
Special Route Handlers
Special Route Handlers like sitemap.ts, opengraph-image.tsx, and icon.tsx, and other metadata files remain static by default unless they use Dynamic APIs or dynamic config options.
Route Resolution
You can consider a route the lowest level routing primitive.
- They do not participate in layouts or client-side navigations like
page. - There cannot be a
route.jsfile at the same route aspage.js.
| Page | Route | Result |
|---|---|---|
app/page.js | app/route.js | Conflict |
app/page.js | app/api/route.js | Valid |
app/[user]/page.js | app/api/route.js | Valid |
Each route.js or page.js file takes over all HTTP verbs for that route.
export default function Page() { return <h1>Hello, Next.js!</h1> } // Conflict // `app/route.ts` export async function POST(request: Request) {}Read more about how Route Handlers complement your frontend application, or explore the Route Handlers API Reference.
Route Context Helper
In TypeScript, you can type the context parameter for Route Handlers with the globally available RouteContext helper:
import type { NextRequest } from 'next/server' export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) { const { id } = await ctx.params return Response.json({ id }) }Good to know
- Types are generated during
next dev,next buildornext typegen.
API Reference
Was this helpful?
