export const dynamic = 'force-dynamic' // defaults to auto
export async function GET(request: Request) {}
Route Handlers can be nested 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 native
Request
and
Response
. Next.js extends them with
NextRequest
and
NextResponse
to provide convenient helpers for advanced use cases.
Behavior
Route Handlers are cached by default when using the
GET
method with the
Response
object.
app/items/route.ts
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 })
TypeScript Warning: Response.json()
is only valid from TypeScript 5.2. If you use a lower TypeScript version, you can use NextResponse.json()
for typed responses instead.
Opting out of cachingUsing the Request
object with the GET
method.
Using any of the other HTTP methods.
Using Dynamic Functions like cookies
and headers
.
The Segment Config Options manually specifies dynamic mode.
For example:
app/products/api/route.tsexport async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const id = searchParams.get('id')
const res = await fetch(`https://data.mongodb-api.com/product/${id}`, {
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY!,
const product = await res.json()
return Response.json({ product })
Similarly, the POST
method will cause the Route Handler to be evaluated dynamically.
app/items/route.tsexport async function POST() {
const res = await fetch('https://data.mongodb-api.com/...', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'API-Key': process.env.DATA_API_KEY!,
body: JSON.stringify({ time: new Date().toISOString() }),
const data = await res.json()
return Response.json(data)
Good to know: Like API Routes, Route Handlers can be used for cases like handling form submissions. A new abstraction for handling forms and mutations that integrates deeply with React is being worked on.
Route ResolutionYou 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.js
file at the same route as page.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.
app/page.jsexport default function Page() {
return <h1>Hello, Next.js!</h1>
// ❌ Conflict
// `app/route.js`
export async function POST(request) {}
ExamplesThe following examples show how to combine Route Handlers with other Next.js APIs and features.
Revalidating Cached Data
You can revalidate cached data using the next.revalidate
option:
app/items/route.tsexport async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
next: { revalidate: 60 }, // Revalidate every 60 seconds
const data = await res.json()
return Response.json(data)
Alternatively, you can use the revalidate
segment config option:
export const revalidate = 60
Dynamic Functions
Route Handlers can be used with dynamic functions from Next.js, like cookies
and headers
.
Cookies
You can read or set cookies with cookies
from next/headers
. This server function can be called directly in a Route Handler, or nested inside of another function.
Alternatively, you can return a new Response
using the Set-Cookie
header.
app/api/route.tsimport { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token.value}` },
You can also use the underlying Web APIs to read cookies from the request (NextRequest
):
app/api/route.tsimport { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
Headers
You can read headers with headers
from next/headers
. This server function can be called directly in a Route Handler, or nested inside of another function.
This headers
instance is read-only. To set headers, you need to return a new Response
with new headers
.
app/api/route.tsimport { headers } from 'next/headers'
export async function GET(request: Request) {
const headersList = headers()
const referer = headersList.get('referer')
return new Response('Hello, Next.js!', {
status: 200,
headers: { referer: referer },
You can also use the underlying Web APIs to read headers from the request (NextRequest
):
app/api/route.tsimport { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
Redirectsapp/api/route.tsimport { redirect } from 'next/navigation'
export async function GET(request: Request) {
redirect('https://nextjs.org/')
Dynamic Route Segments
We recommend reading the Defining Routes page before continuing.
Route Handlers can use Dynamic Segments to create request handlers from dynamic data.
app/items/[slug]/route.tsexport async function GET(
request: Request,
{ params }: { params: { slug: string } }
const slug = params.slug // 'a', 'b', or 'c'
Route Example URL params
app/items/[slug]/route.js
/items/a
{ slug: 'a' }
app/items/[slug]/route.js
/items/b
{ slug: 'b' }
app/items/[slug]/route.js
/items/c
{ slug: 'c' }
URL Query Parameters
The request object passed to the Route Handler is a NextRequest
instance, which has some additional convenience methods, including for more easily handling query parameters.
app/api/search/route.tsimport { type NextRequest } from 'next/server'
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
// query is "hello" for /api/search?query=hello
Streaming
Streaming is commonly used in combination with Large Language Models (LLMs), such as OpenAI, for AI-generated content. Learn more about the AI SDK.
app/api/chat/route.tsimport OpenAI from 'openai'
import { OpenAIStream, StreamingTextResponse } from 'ai'
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
export const runtime = 'edge'
export async function POST(req: Request) {
const { messages } = await req.json()
const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages,
const stream = OpenAIStream(response)
return new StreamingTextResponse(stream)
These abstractions use the Web APIs to create a stream. You can also use the underlying Web APIs directly.
app/api/route.ts// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator: any) {
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next()
if (done) {
controller.close()
} else {
controller.enqueue(value)
function sleep(time: number) {
return new Promise((resolve) => {
setTimeout(resolve, time)
const encoder = new TextEncoder()
async function* makeIterator() {
yield encoder.encode('<p>One</p>')
await sleep(200)
yield encoder.encode('<p>Two</p>')
await sleep(200)
yield encoder.encode('<p>Three</p>')
export async function GET() {
const iterator = makeIterator()
const stream = iteratorToStream(iterator)
return new Response(stream)
Request BodyYou can read the Request
body using the standard Web API methods:
app/items/route.tsexport async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
Request Body FormDataYou can read the FormData
using the request.formData()
function:
app/items/route.tsexport async function POST(request: Request) {
const formData = await request.formData()
const name = formData.get('name')
const email = formData.get('email')
return Response.json({ name, email })
Since formData
data are all strings, you may want to use zod-form-data
to validate the request and retrieve data in the format you prefer (e.g. number
).
You can set CORS headers for a specific Route Handler using the standard Web API methods:
app/api/route.tsexport const dynamic = 'force-dynamic' // defaults to auto
export async function GET(request: Request) {
return new Response('Hello, Next.js!', {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
Good to know:
To add CORS headers to multiple Route Handlers, you can use Middleware or the next.config.js
file.
Alternatively, see our CORS example package.
WebhooksYou can use a Route Handler to receive webhooks from third-party services:
app/api/route.tsexport async function POST(request: Request) {
try {
const text = await request.text()
// Process the webhook payload
} catch (error) {
return new Response(`Webhook error: ${error.message}`, {
status: 400,
return new Response('Success!', {
status: 200,
Notably, unlike API Routes with the Pages Router, you do not need to use bodyParser
to use any additional configuration.
Edge and Node.js Runtimes
Route Handlers have an isomorphic Web API to support both Edge and Node.js runtimes seamlessly, including support for streaming. Since Route Handlers use the same route segment configuration as Pages and Layouts, they support long-awaited features like general-purpose statically regenerated Route Handlers.
You can use the runtime
segment config option to specify the runtime:
export const runtime = 'edge' // 'nodejs' is the default
Non-UI Responses
You can use Route Handlers to return non-UI content. Note that sitemap.xml
, robots.txt
, app icons
, and open graph images all have built-in support.
app/rss.xml/route.tsexport const dynamic = 'force-dynamic' // defaults to auto
export async function GET() {
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>Next.js Documentation</title>
<link>https://nextjs.org/docs</link>
<description>The React Framework for the Web</description>
</channel>
</rss>`,
headers: {
'Content-Type': 'text/xml',
Segment Config Options
Route Handlers use the same route segment configuration as pages and layouts.
app/items/route.tsexport const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
GET
method with the
Response
object.
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 })
TypeScript Warning:
Response.json()
is only valid from TypeScript 5.2. If you use a lower TypeScript version, you can useNextResponse.json()
for typed responses instead.Opting out of caching
Using the Request
object with theGET
method.Using any of the other HTTP methods. Using Dynamic Functions like cookies
andheaders
.The Segment Config Options manually specifies dynamic mode. For example:
app/products/api/route.tsexport async function GET(request: Request) { const { searchParams } = new URL(request.url) const id = searchParams.get('id') const res = await fetch(`https://data.mongodb-api.com/product/${id}`, { headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY!, const product = await res.json() return Response.json({ product })
Similarly, the
POST
method will cause the Route Handler to be evaluated dynamically.app/items/route.tsexport async function POST() { const res = await fetch('https://data.mongodb-api.com/...', { method: 'POST', headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY!, body: JSON.stringify({ time: new Date().toISOString() }), const data = await res.json() return Response.json(data)
Good to know: Like API Routes, Route Handlers can be used for cases like handling form submissions. A new abstraction for handling forms and mutations that integrates deeply with React is being worked on.
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.js
file 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
orpage.js
file takes over all HTTP verbs for that route.app/page.jsexport default function Page() { return <h1>Hello, Next.js!</h1> // ❌ Conflict // `app/route.js` export async function POST(request) {}
Examples
The following examples show how to combine Route Handlers with other Next.js APIs and features.
Revalidating Cached Data
You can revalidate cached data using the
next.revalidate
option:app/items/route.tsexport async function GET() { const res = await fetch('https://data.mongodb-api.com/...', { next: { revalidate: 60 }, // Revalidate every 60 seconds const data = await res.json() return Response.json(data)
Alternatively, you can use the
revalidate
segment config option:export const revalidate = 60
Dynamic Functions
Route Handlers can be used with dynamic functions from Next.js, like
cookies
andheaders
.Cookies
You can read or set cookies with
cookies
fromnext/headers
. This server function can be called directly in a Route Handler, or nested inside of another function.Alternatively, you can return a new
Response
using theSet-Cookie
header.app/api/route.tsimport { cookies } from 'next/headers' export async function GET(request: Request) { const cookieStore = cookies() const token = cookieStore.get('token') return new Response('Hello, Next.js!', { status: 200, headers: { 'Set-Cookie': `token=${token.value}` },
You can also use the underlying Web APIs to read cookies from the request (
NextRequest
):app/api/route.tsimport { type NextRequest } from 'next/server' export async function GET(request: NextRequest) { const token = request.cookies.get('token')
Headers
You can read headers with
headers
fromnext/headers
. This server function can be called directly in a Route Handler, or nested inside of another function.This
headers
instance is read-only. To set headers, you need to return a newResponse
with newheaders
.app/api/route.tsimport { headers } from 'next/headers' export async function GET(request: Request) { const headersList = headers() const referer = headersList.get('referer') return new Response('Hello, Next.js!', { status: 200, headers: { referer: referer },
You can also use the underlying Web APIs to read headers from the request (
NextRequest
):app/api/route.tsimport { type NextRequest } from 'next/server' export async function GET(request: NextRequest) { const requestHeaders = new Headers(request.headers)
Redirects
app/api/route.tsimport { redirect } from 'next/navigation' export async function GET(request: Request) { redirect('https://nextjs.org/')
Dynamic Route Segments
We recommend reading the Defining Routes page before continuing.
Route Handlers can use Dynamic Segments to create request handlers from dynamic data.
app/items/[slug]/route.tsexport async function GET( request: Request, { params }: { params: { slug: string } } const slug = params.slug // 'a', 'b', or 'c'
Route Example URL params
app/items/[slug]/route.js
/items/a
{ slug: 'a' }
app/items/[slug]/route.js
/items/b
{ slug: 'b' }
app/items/[slug]/route.js
/items/c
{ slug: 'c' }
URL Query Parameters
The request object passed to the Route Handler is a
NextRequest
instance, which has some additional convenience methods, including for more easily handling query parameters.app/api/search/route.tsimport { type NextRequest } from 'next/server' export function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const query = searchParams.get('query') // query is "hello" for /api/search?query=hello
Streaming
Streaming is commonly used in combination with Large Language Models (LLMs), such as OpenAI, for AI-generated content. Learn more about the AI SDK.
app/api/chat/route.tsimport OpenAI from 'openai' import { OpenAIStream, StreamingTextResponse } from 'ai' const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, export const runtime = 'edge' export async function POST(req: Request) { const { messages } = await req.json() const response = await openai.chat.completions.create({ model: 'gpt-3.5-turbo', stream: true, messages, const stream = OpenAIStream(response) return new StreamingTextResponse(stream)
These abstractions use the Web APIs to create a stream. You can also use the underlying Web APIs directly.
app/api/route.ts// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream function iteratorToStream(iterator: any) { return new ReadableStream({ async pull(controller) { const { value, done } = await iterator.next() if (done) { controller.close() } else { controller.enqueue(value) function sleep(time: number) { return new Promise((resolve) => { setTimeout(resolve, time) const encoder = new TextEncoder() async function* makeIterator() { yield encoder.encode('<p>One</p>') await sleep(200) yield encoder.encode('<p>Two</p>') await sleep(200) yield encoder.encode('<p>Three</p>') export async function GET() { const iterator = makeIterator() const stream = iteratorToStream(iterator) return new Response(stream)
Request Body
You can read the
Request
body using the standard Web API methods:app/items/route.tsexport async function POST(request: Request) { const res = await request.json() return Response.json({ res })
Request Body FormData
You can read the
FormData
using therequest.formData()
function:app/items/route.tsexport async function POST(request: Request) { const formData = await request.formData() const name = formData.get('name') const email = formData.get('email') return Response.json({ name, email })
Since
formData
data are all strings, you may want to usezod-form-data
to validate the request and retrieve data in the format you prefer (e.g.number
).You can set CORS headers for a specific Route Handler using the standard Web API methods:
app/api/route.tsexport const dynamic = 'force-dynamic' // defaults to auto export async function GET(request: Request) { return new Response('Hello, Next.js!', { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
Good to know:
To add CORS headers to multiple Route Handlers, you can use Middleware or the next.config.js
file.Alternatively, see our CORS example package. Webhooks
You can use a Route Handler to receive webhooks from third-party services:
app/api/route.tsexport async function POST(request: Request) { try { const text = await request.text() // Process the webhook payload } catch (error) { return new Response(`Webhook error: ${error.message}`, { status: 400, return new Response('Success!', { status: 200,
Notably, unlike API Routes with the Pages Router, you do not need to use
bodyParser
to use any additional configuration.
Edge and Node.js Runtimes
Route Handlers have an isomorphic Web API to support both Edge and Node.js runtimes seamlessly, including support for streaming. Since Route Handlers use the same route segment configuration as Pages and Layouts, they support long-awaited features like general-purpose statically regenerated Route Handlers.
You can use the
runtime
segment config option to specify the runtime:export const runtime = 'edge' // 'nodejs' is the default
Non-UI Responses
You can use Route Handlers to return non-UI content. Note that
sitemap.xml
,robots.txt
,app icons
, and open graph images all have built-in support.app/rss.xml/route.tsexport const dynamic = 'force-dynamic' // defaults to auto export async function GET() { return new Response( `<?xml version="1.0" encoding="UTF-8" ?> <rss version="2.0"> <channel> <title>Next.js Documentation</title> <link>https://nextjs.org/docs</link> <description>The React Framework for the Web</description> </channel> </rss>`, headers: { 'Content-Type': 'text/xml',
Segment Config Options
Route Handlers use the same route segment configuration as pages and layouts.
app/items/route.tsexport const dynamic = 'auto' export const dynamicParams = true export const revalidate = false export const fetchCache = 'auto' export const runtime = 'nodejs'