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
app
directory. They are the equivalent of API Routes inside thepages
directory meaning you do not need to use API Routes and Route Handlers together.
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 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
.
The following HTTP methods are supported: GET
, POST
, PUT
, PATCH
, DELETE
, HEAD
, and OPTIONS
.
NextRequest
and NextResponse
APIsIn addition to supporting native Request and Response. Next.js extends them with
NextRequest
and NextResponse
to provide convenient helpers for advanced use cases.
Route Handlers are statically evaluated by default when using the GET
method with the Response
object.
import { NextResponse } from 'next/server';
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 NextResponse.json({ data })
}
TypeScript Warning: Although
Response.json()
is valid, native TypeScript types currently shows an error, you can useNextResponse.json()
for typed responses instead.
Static fetching can be beneficial to generate custom route handlers for robots.txt
, rss.xml
, sitemap.xml
, and other routes. See Example.
Route handlers are evaluated dynamically when:
Request
object with the GET
method.cookies
and headers
.For example:
import { NextResponse } from 'next/server';
export 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 NextResponse.json({ product })
}
Similarly, the POST
method will cause the Route Handler to be evaluated dynamically.
import { NextResponse } from 'next/server';
export 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 NextResponse.json(data);
}
Note: Previously, API Routes could have been used for use cases like handling form submissions. Route Handlers are likely not the solution for these uses cases. We will be recommending the use of mutations for this when ready.
You can consider a route
the lowest level routing primitive.
page
.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.js`
export default function Page() {
return <h1>Hello, Next.js!</h1>;
}
// ❌ Conflict
// `app/route.js`
export async function POST(request) {}
The following examples show how to combine Route Handlers with other Next.js APIs and features.
You can revalidate static data fetches using the next.revalidate
option:
import { NextResponse } from 'next/server';
export 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 NextResponse.json(data)
}
Alternatively, you can use the revalidate
segment config option:
export const revalidate = 60;
Route Handlers can be used with dynamic functions from Next.js, like cookies
and headers
.
You can read cookies with cookies
from next/headers
. This server function can be called directly in a Route Handler, or nested inside of another function.
This cookies
instance is read-only. To set cookies, you need to return a new Response
using the Set-Cookie
header.
import { 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}` }
});
}
Alternatively, you can use abstractions on top of the underlying Web APIs to read cookies (NextRequest
):
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
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
.
import { 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 }
});
}
Alternatively, you can use abstractions on top of the underlying Web APIs to read headers (NextRequest
):
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
}
import { redirect } from 'next/navigation';
export async function GET(request: Request) {
redirect('https://nextjs.org/')
}
We recommend reading the Defining Routes page before continuing.
Route Handlers can use Dynamic Segments to create request handlers from dynamic data.
export async function GET(request, { params }) {
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' } |
Note: Using generateStaticParams() is not yet supported for use with Route Handlers.
// https://developer.mozilla.org/en-US/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) {
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)
}
You can read the Request
body using the standard Web API methods:
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const res = await request.json();
return NextResponse.json({ res })
}
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 = 'experimental-edge';
robots.txt
, rss.xml
, and sitemap.xml
You can use Route Handlers to return non-UI content such as robots.txt
, rss.xml
, and sitemap.xml
.
export async function GET() {
return new Response(`User-agent: *
Allow: /
Sitemap: https://www.example.com/sitemap.xml`);
}
Route Handlers use the same route segment configuration as pages and layouts.
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
See the API reference for more details.