Manejadores de Ruta (Route Handlers)
Los Manejadores de Ruta (Route Handlers) te permiten crear manejadores de solicitud personalizados para una ruta específica utilizando las APIs Request y Response de la Web.

Es bueno saberlo: Los Manejadores de Ruta solo están disponibles dentro del directorio
app
. Son equivalentes a las Rutas API dentro del directoriopages
, lo que significa que no necesitas usar Rutas API y Manejadores de Ruta juntos.
Convención
Los Manejadores de Ruta se definen en un archivo route.js|ts
dentro del directorio app
:
export const dynamic = 'force-dynamic' // defaults to auto
export async function GET(request: Request) {}
export const dynamic = 'force-dynamic' // defaults to auto
export async function GET(request) {}
Los Manejadores de Ruta pueden anidarse dentro del directorio app
, similar a page.js
y layout.js
. Pero no puede haber un archivo route.js
en el mismo segmento de ruta que page.js
.
Métodos HTTP soportados
Se soportan los siguientes métodos HTTP: GET
, POST
, PUT
, PATCH
, DELETE
, HEAD
y OPTIONS
. Si se llama a un método no soportado, Next.js devolverá una respuesta 405 Método No Permitido
.
APIs extendidas NextRequest
y NextResponse
Además de soportar las APIs nativas Request y Response, Next.js las extiende con NextRequest
y NextResponse
para proporcionar ayudantes convenientes en casos de uso avanzados.
Comportamiento
Caché
Los Manejadores de Ruta se almacenan en caché por defecto cuando se usa el método GET
con el objeto Response
.
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 })
}
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 })
}
Advertencia de TypeScript:
Response.json()
solo es válido a partir de TypeScript 5.2. Si usas una versión inferior de TypeScript, puedes usarNextResponse.json()
para respuestas tipadas.
Exclusión de la caché
Puedes excluirte del almacenamiento en caché mediante:
- Usar el objeto
Request
con el métodoGET
. - Usar cualquier otro método HTTP.
- Usar Funciones Dinámicas como
cookies
yheaders
. - Especificar manualmente el modo dinámico en las Opciones de Configuración de Segmento.
Por ejemplo:
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 Response.json({ product })
}
export async function GET(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 })
}
De manera similar, el método POST
hará que el Manejador de Ruta se evalúe dinámicamente.
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 Response.json(data)
}
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 Response.json(data)
}
Es bueno saberlo: Al igual que las Rutas API, los Manejadores de Ruta pueden usarse para casos como el manejo de envíos de formularios. Se está trabajando en una nueva abstracción para manejar formularios y mutaciones que se integra profundamente con React.
Resolución de Rutas
Puedes considerar un route
como la primitiva de enrutamiento de nivel más bajo.
- No participan en diseños o navegaciones del lado del cliente como
page
. - No puede haber un archivo
route.js
en la misma ruta quepage.js
.
Página | Ruta | Resultado |
---|---|---|
app/page.js | app/route.js | Conflicto |
app/page.js | app/api/route.js | Válido |
app/[user]/page.js | app/api/route.js | Válido |
Cada archivo route.js
o page.js
toma todos los verbos HTTP para esa ruta.
export default function Page() {
return <h1>Hello, Next.js!</h1>
}
// ❌ Conflicto
// `app/route.js`
export async function POST(request) {}
Ejemplos
Los siguientes ejemplos muestran cómo combinar Manejadores de Ruta con otras APIs y características de Next.js.
Revalidación de Datos en Caché
Puedes revalidar datos en caché usando la opción next.revalidate
:
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
next: { revalidate: 60 }, // Revalida cada 60 segundos
})
const data = await res.json()
return Response.json(data)
}
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
next: { revalidate: 60 }, // Revalida cada 60 segundos
})
const data = await res.json()
return Response.json(data)
}
Alternativamente, puedes usar la opción de configuración de segmento revalidate
:
export const revalidate = 60
Funciones Dinámicas
Los Manejadores de Ruta pueden usarse con funciones dinámicas de Next.js, como cookies
y headers
.
Cookies
Puedes leer o establecer cookies con cookies
de next/headers
. Esta función de servidor puede llamarse directamente en un Manejador de Ruta o anidarse dentro de otra función.
Alternativamente, puedes devolver una nueva Response
usando la cabecera Set-Cookie
.
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.value}` },
})
}
import { cookies } from 'next/headers'
export async function GET(request) {
const cookieStore = cookies()
const token = cookieStore.get('token')
return new Response('Hello, Next.js!', {
status: 200,
headers: { 'Set-Cookie': `token=${token}` },
})
}
También puedes usar las APIs Web subyacentes para leer cookies de la solicitud (NextRequest
):
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.cookies.get('token')
}
export async function GET(request) {
const token = request.cookies.get('token')
}
Cabeceras
Puedes leer cabeceras con headers
de next/headers
. Esta función de servidor puede llamarse directamente en un Manejador de Ruta o anidarse dentro de otra función.
Esta instancia de headers
es de solo lectura. Para establecer cabeceras, debes devolver una nueva Response
con nuevas 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 },
})
}
import { headers } from 'next/headers'
export async function GET(request) {
const headersList = headers()
const referer = headersList.get('referer')
return new Response('Hello, Next.js!', {
status: 200,
headers: { referer: referer },
})
}
También puedes usar las APIs Web subyacentes para leer cabeceras de la solicitud (NextRequest
):
import { type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const requestHeaders = new Headers(request.headers)
}
export async function GET(request) {
const requestHeaders = new Headers(request.headers)
}
Redirecciones
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
redirect('https://nextjs.org/')
}
import { redirect } from 'next/navigation'
export async function GET(request) {
redirect('https://nextjs.org/')
}
Segmentos de Ruta Dinámicos
Recomendamos leer la página Definiendo Rutas antes de continuar.
Los Manejadores de Ruta pueden usar Segmentos Dinámicos para crear manejadores de solicitud a partir de datos dinámicos.
export async function GET(
request: Request,
{ params }: { params: { slug: string } }
) {
const slug = params.slug // 'a', 'b', o 'c'
}
export async function GET(request, { params }) {
const slug = params.slug // 'a', 'b', o 'c'
}
Ruta | URL de ejemplo | 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' } |
Parámetros de Consulta URL
El objeto de solicitud pasado al Manejador de Ruta es una instancia de NextRequest
, que tiene algunos métodos adicionales convenientes, incluyendo para manejar más fácilmente los parámetros de consulta.
import { type NextRequest } from 'next/server'
export function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
// query es "hello" para /api/search?query=hello
}
export function GET(request) {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('query')
// query es "hello" para /api/search?query=hello
}
Streaming
El streaming se usa comúnmente en combinación con Modelos de Lenguaje Grandes (LLMs), como OpenAI, para contenido generado por IA. Aprende más sobre el AI SDK.
import 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)
}
import 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) {
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)
}
Estas abstracciones usan las APIs Web para crear un stream. También puedes usar las APIs Web subyacentes directamente.
// 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)
}
// 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)
}
Cuerpo de la solicitud (Request Body)
Puedes leer el cuerpo de la Request
utilizando los métodos estándar de la Web API:
export async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
}
export async function POST(request) {
const res = await request.json()
return Response.json({ res })
}
FormData en el cuerpo de la solicitud (Request Body FormData)
Puedes leer el FormData
usando la función request.formData()
:
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 })
}
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 })
}
Dado que los datos de formData
son todos strings, puedes usar zod-form-data
para validar la solicitud y obtener los datos en el formato que prefieras (por ejemplo, number
).
CORS
Puedes configurar los encabezados CORS para un Manejador de Ruta (Route Handler) específico usando los métodos estándar de la Web API:
export 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',
},
})
}
export const dynamic = 'force-dynamic' // defaults to auto
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',
},
})
}
Nota importante:
- Para agregar encabezados CORS a múltiples Manejadores de Ruta, puedes usar Middleware o el archivo
next.config.js
.- Alternativamente, consulta nuestro paquete de ejemplo CORS.
Webhooks
Puedes usar un Manejador de Ruta para recibir webhooks de servicios de terceros:
export async function POST(request: Request) {
try {
const text = await request.text()
// Procesar el payload del webhook
} catch (error) {
return new Response(`Error en el webhook: ${error.message}`, {
status: 400,
})
}
return new Response('¡Éxito!', {
status: 200,
})
}
export async function POST(request) {
try {
const text = await request.text()
// Procesar el payload del webhook
} catch (error) {
return new Response(`Error en el webhook: ${error.message}`, {
status: 400,
})
}
return new Response('¡Éxito!', {
status: 200,
})
}
Es importante destacar que, a diferencia de las API Routes con el Pages Router, no necesitas usar bodyParser
ni ninguna configuración adicional.
Entornos de ejecución Edge y Node.js
Los Manejadores de Ruta tienen una Web API isomórfica para soportar tanto entornos Edge como Node.js sin problemas, incluyendo soporte para streaming. Dado que los Manejadores de Ruta usan la misma configuración de segmento de ruta que las páginas y layouts, soportan características muy esperadas como los Manejadores de Ruta regenerados estáticamente de propósito general.
Puedes usar la opción de configuración runtime
para especificar el entorno de ejecución:
export const runtime = 'edge' // 'nodejs' es el valor por defecto
Respuestas no-UI
Puedes usar Manejadores de Ruta para devolver contenido que no sea UI. Ten en cuenta que sitemap.xml
, robots.txt
, app icons
y open graph images tienen soporte incorporado.
export 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',
},
}
)
}
export 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>`)
}
Opciones de configuración de segmento
Los Manejadores de Ruta usan la misma configuración de segmento de ruta que las páginas y 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'
export const dynamic = 'auto'
export const dynamicParams = true
export const revalidate = false
export const fetchCache = 'auto'
export const runtime = 'nodejs'
export const preferredRegion = 'auto'
Consulta la referencia de API para más detalles.