Files
next.js/docs/01-app/03-api-reference/03-file-conventions/route.mdx
Delba de OliveiraGitHubVercel <vercel[bot]@users.noreply.github.com>Joseph
c2b4c0815c Unify caching story across the docs (#90149)
This PRs unifies the caching story across the docs, making Cache
Components the happy path, while still providing guidance to users in
the old model. However, instead of explaining the old model and its
caching layers, we've created a new guide focusing on what APIs to use
and when.

This follow-up PR aligns terminology across the docs:
https://github.com/vercel/next.js/pull/90589

## IA updates

Getting Started section: 

- Improves Getting Started progression:
- **Before:** CC → Fetching Data → Updating Data → Caching and
Revalidating (old and new model mixed)
- **After:** Fetching Data (Dynamic) → Mutating Data (Dynamic) → Caching
with CC (Prerendering) → Revalidating with CC.
- New: `caching.mdx` (CC-first)
   - Structure: 
      - Enabling Cache Components
      - Data vs UI-level caching
      - Working with request time APIs
      - Passing request values to cached functions
      - Working with non-deterministic operations
      - Working with synchronous operations
      - How rendering works (PPR and static shell story)
- New: `revalidating.mdx` (CC-first)
   - Explains how to use `cacheLife` and `cacheTag`

Guides Section: 

- New: `caching-and-revalidating.mdx` (Previous Model)
- For users who are not using CC, includes `fetch` options and route
segment config
- Moves route segment config options that don't apply to CC from API
reference to this guide (for easy archiving in the future).
- New: `migrating-to-cache-components.mdx` (WIP)
- Del: `caching.mdx` 😌 

## Terminology

We should remove caching layers from the docs. Users only needed to be
exposed to them when they were configured independently, but the new CC
APIs work across layers.

To make it easier to review this PR, I'm consolidating terminology and
fixing broken links in a new PR:
https://github.com/vercel/next.js/pull/90589

---------

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Joseph <[email protected]>
2026-03-03 13:14:24 +00:00

671 lines
20 KiB
Plaintext

---
title: route.js
description: API reference for the route.js special file.
---
Route Handlers allow you to create custom request handlers for a given route using the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) and [Response](https://developer.mozilla.org/docs/Web/API/Response) APIs.
```ts filename="route.ts" switcher
export async function GET() {
return Response.json({ message: 'Hello World' })
}
```
```js filename="route.js" switcher
export async function GET() {
return Response.json({ message: 'Hello World' })
}
```
## Reference
### HTTP Methods
A **route** file allows you to create custom request handlers for a given route. The following [HTTP methods](https://developer.mozilla.org/docs/Web/HTTP/Methods) are supported: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS`.
```ts filename="route.ts" switcher
export async function GET(request: Request) {}
export async function HEAD(request: Request) {}
export async function POST(request: Request) {}
export async function PUT(request: Request) {}
export async function DELETE(request: Request) {}
export async function PATCH(request: Request) {}
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the Route Handler.
export async function OPTIONS(request: Request) {}
```
```js filename="route.js" switcher
export async function GET(request) {}
export async function HEAD(request) {}
export async function POST(request) {}
export async function PUT(request) {}
export async function DELETE(request) {}
export async function PATCH(request) {}
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the Route Handler.
export async function OPTIONS(request) {}
```
### Parameters
#### `request` (optional)
The `request` object is a [NextRequest](/docs/app/api-reference/functions/next-request) object, which is an extension of the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) API. `NextRequest` gives you further control over the incoming request, including easily accessing `cookies` and an extended, parsed, URL object `nextUrl`.
```ts filename="route.ts" switcher
import type { NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const url = request.nextUrl
}
```
```js filename="route.js" switcher
export async function GET(request) {
const url = request.nextUrl
}
```
#### `context` (optional)
- **`params`**: a promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) for the current route.
```ts filename="app/dashboard/[team]/route.ts" switcher
export async function GET(
request: Request,
{ params }: { params: Promise<{ team: string }> }
) {
const { team } = await params
}
```
```js filename="app/dashboard/[team]/route.js" switcher
export async function GET(request, { params }) {
const { team } = await params
}
```
| Example | URL | `params` |
| -------------------------------- | -------------- | ---------------------------------- |
| `app/dashboard/[team]/route.js` | `/dashboard/1` | `Promise<{ team: '1' }>` |
| `app/shop/[tag]/[item]/route.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
| `app/blog/[...slug]/route.js` | `/blog/1/2` | `Promise<{ slug: ['1', '2'] }>` |
### Route Context Helper
You can type the Route Handler context using `RouteContext` to get strongly typed `params` from a route literal. `RouteContext` is a globally available helper.
```ts filename="app/users/[id]/route.ts"
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 build` or `next typegen`.
> - After type generation, the `RouteContext` helper is globally available. It doesn't need to be imported.
## Examples
### Cookies
You can read or set cookies with [`cookies`](/docs/app/api-reference/functions/cookies) from `next/headers`.
```ts filename="route.ts" switcher
import { cookies } from 'next/headers'
export async function GET(request: NextRequest) {
const cookieStore = await cookies()
const a = cookieStore.get('a')
const b = cookieStore.set('b', '1')
const c = cookieStore.delete('c')
}
```
```js filename="route.js" switcher
import { cookies } from 'next/headers'
export async function GET(request) {
const cookieStore = await cookies()
const a = cookieStore.get('a')
const b = cookieStore.set('b', '1')
const c = cookieStore.delete('c')
}
```
Alternatively, you can return a new `Response` using the [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) header.
```ts filename="app/api/route.ts" switcher
import { cookies } from 'next/headers'
export async function GET(request: Request) {
const cookieStore = await cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token.value}` },
})
}
```
```js filename="app/api/route.js" switcher
import { cookies } from 'next/headers'
export async function GET(request) {
const cookieStore = await 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`](/docs/app/api-reference/functions/next-request)):
```ts filename="app/api/route.ts" switcher
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
```
```js filename="app/api/route.js" switcher
export async function GET(request) {
const token = request.cookies.get('token')
}
```
### Headers
You can read headers with [`headers`](/docs/app/api-reference/functions/headers) from `next/headers`.
```ts filename="route.ts" switcher
import { headers } from 'next/headers'
import type { NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const headersList = await headers()
const referer = headersList.get('referer')
}
```
```js filename="route.js" switcher
import { headers } from 'next/headers'
export async function GET(request) {
const headersList = await headers()
const referer = headersList.get('referer')
}
```
This `headers` instance is read-only. To set headers, you need to return a new `Response` with new `headers`.
```ts filename="app/api/route.ts" switcher
import { headers } from 'next/headers'
export async function GET(request: Request) {
const headersList = await headers()
const referer = headersList.get('referer')
return new Response('Hello, Next.js!', {
status: 200,
headers: { referer: referer },
})
}
```
```js filename="app/api/route.js" switcher
import { headers } from 'next/headers'
export async function GET(request) {
const headersList = await 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`](/docs/app/api-reference/functions/next-request)):
```ts filename="app/api/route.ts" switcher
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
}
```
```js filename="app/api/route.js" switcher
export async function GET(request) {
const requestHeaders = new Headers(request.headers)
}
```
### Revalidating Cached Data
You can [revalidate cached data](/docs/app/guides/incremental-static-regeneration) using the `revalidate` route segment config option.
```ts filename="app/posts/route.ts" switcher
export const revalidate = 60
export async function GET() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return Response.json(posts)
}
```
```js filename="app/posts/route.js" switcher
export const revalidate = 60
export async function GET() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return Response.json(posts)
}
```
### Redirects
```ts filename="app/api/route.ts" switcher
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
redirect('https://nextjs.org/')
}
```
```js filename="app/api/route.js" switcher
import { redirect } from 'next/navigation'
export async function GET(request) {
redirect('https://nextjs.org/')
}
```
### Dynamic Route Segments
Route Handlers can use [Dynamic Segments](/docs/app/api-reference/file-conventions/dynamic-routes) to create request handlers from dynamic data.
```ts filename="app/items/[slug]/route.ts" switcher
export async function GET(
request: Request,
{ params }: { params: Promise<{ slug: string }> }
) {
const { slug } = await params // 'a', 'b', or 'c'
}
```
```js filename="app/items/[slug]/route.js" switcher
export async function GET(request, { params }) {
const { slug } = await params // 'a', 'b', or 'c'
}
```
| Route | Example URL | `params` |
| --------------------------- | ----------- | ------------------------ |
| `app/items/[slug]/route.js` | `/items/a` | `Promise<{ slug: 'a' }>` |
| `app/items/[slug]/route.js` | `/items/b` | `Promise<{ slug: 'b' }>` |
| `app/items/[slug]/route.js` | `/items/c` | `Promise<{ slug: 'c' }>` |
#### Static Generation with `generateStaticParams`
You can use [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) with dynamic Route Handlers to statically generate responses at build time for specified params, while handling other params dynamically at request time.
When using [Cache Components](/docs/app/getting-started/caching), you can combine `generateStaticParams` with `use cache` to enable data caching for both prerendered and runtime params.
See the [generateStaticParams with Route Handlers](/docs/app/api-reference/functions/generate-static-params#with-route-handlers) documentation for examples and details.
### URL Query Parameters
The request object passed to the Route Handler is a `NextRequest` instance, which includes [some additional convenience methods](/docs/app/api-reference/functions/next-request#nexturl), such as those for more easily handling query parameters.
```ts filename="app/api/search/route.ts" switcher
import { 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
}
```
```js filename="app/api/search/route.js" switcher
export function GET(request) {
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](https://sdk.vercel.ai/docs/introduction).
```ts filename="app/api/chat/route.ts" switcher
import { openai } from '@ai-sdk/openai'
import { StreamingTextResponse, streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
})
return new StreamingTextResponse(result.toAIStream())
}
```
```js filename="app/api/chat/route.js" switcher
import { openai } from '@ai-sdk/openai'
import { StreamingTextResponse, streamText } from 'ai'
export async function POST(req) {
const { messages } = await req.json()
const result = await streamText({
model: openai('gpt-4-turbo'),
messages,
})
return new StreamingTextResponse(result.toAIStream())
}
```
These abstractions use the Web APIs to create a stream. You can also use the underlying Web APIs directly.
```ts filename="app/api/route.ts" switcher
// 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)
}
```
```js filename="app/api/route.js" switcher
// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator) {
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)
}
```
### Request Body
You can read the `Request` body using the standard Web API methods:
```ts filename="app/items/route.ts" switcher
export async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
}
```
```js filename="app/items/route.js" switcher
export async function POST(request) {
const res = await request.json()
return Response.json({ res })
}
```
### Request Body FormData
You can read the `FormData` using the `request.formData()` function:
```ts filename="app/items/route.ts" switcher
export 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 })
}
```
```js filename="app/items/route.js" switcher
export async function POST(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`](https://www.npmjs.com/zod-form-data) to validate the request and retrieve data in the format you prefer (e.g. `number`).
### CORS
You can set CORS headers for a specific Route Handler using the standard Web API methods:
```ts filename="app/api/route.ts" switcher
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',
},
})
}
```
```js filename="app/api/route.js" switcher
export async function GET(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 [Proxy](/docs/app/api-reference/file-conventions/proxy#cors) or the [`next.config.js` file](/docs/app/api-reference/config/next-config-js/headers#cors).
### Webhooks
You can use a Route Handler to receive webhooks from third-party services:
```ts filename="app/api/route.ts" switcher
export 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,
})
}
```
```js filename="app/api/route.js" switcher
export async function POST(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.
### Non-UI Responses
You can use Route Handlers to return non-UI content. Note that [`sitemap.xml`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts), [`robots.txt`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file), [`app icons`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx), and [open graph images](/docs/app/api-reference/file-conventions/metadata/opengraph-image) all have built-in support.
```ts filename="app/rss.xml/route.ts" switcher
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',
},
}
)
}
```
```js filename="app/rss.xml/route.js" switcher
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>`)
}
```
### Segment Config Options
Route Handlers use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) as pages and layouts.
```ts filename="app/items/route.ts" switcher
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
```
```js filename="app/items/route.js" switcher
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](/docs/app/api-reference/file-conventions/route-segment-config) for more details.
## Version History
| Version | Changes |
| ------------ | ---------------------------------------------------------------------------------------------------- |
| `v15.0.0-RC` | `context.params` is now a promise. A [codemod](/docs/app/guides/upgrading/codemods#150) is available |
| `v15.0.0-RC` | The default caching for `GET` handlers was changed from static to dynamic |
| `v13.2.0` | Route Handlers are introduced. |