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.

Archivo especial Route.js

Importante: Los Manejadores de Ruta solo están disponibles dentro del directorio app. Son equivalentes a las Rutas API dentro del directorio pages, 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 async function GET(request: Request) {}
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 para 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 usar NextResponse.json() para respuestas tipadas.

Exclusión de caché

Puedes excluirte del caché mediante:

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)
}

Importante: 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 Ruta

Puedes considerar un route como la primitiva de enrutamiento de nivel más bajo.

  • No participan en diseños (layouts) o navegaciones del lado del cliente como page.
  • No puede haber un archivo route.js en la misma ruta que page.js.
PáginaRutaResultado
app/page.jsapp/route.jsCross Icon Conflicto
app/page.jsapp/api/route.jsCheck Icon Válido
app/[user]/page.jsapp/api/route.jsCheck Icon Válido

Cada archivo route.js o page.js toma todos los verbos HTTP para esa ruta.

app/page.js
export default function Page() {
  return <h1>¡Hola, 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 }, // Revalidar 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 }, // Revalidar 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 cookies con cookies desde next/headers. Esta función de servidor puede llamarse directamente en un Manejador de Ruta o anidarse dentro de otra función.

Esta instancia cookies es de solo lectura. Para establecer cookies, debes 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('¡Hola, 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('¡Hola, Next.js!', {
    status: 200,
    headers: { 'Set-Cookie': `token=${token}` },
  })
}

Alternativamente, puedes usar abstracciones sobre las APIs web subyacentes para leer cookies (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 desde next/headers. Esta función de servidor puede llamarse directamente en un Manejador de Ruta o anidarse dentro de otra función.

Esta instancia 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('¡Hola, 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('¡Hola, Next.js!', {
    status: 200,
    headers: { referer: referer },
  })
}

Alternativamente, puedes usar abstracciones sobre las APIs web subyacentes para leer cabeceras (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'
}
RutaURL de ejemploparams
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 utiliza comúnmente en combinación con Modelos de Lenguaje a Gran Escala (LLMs, por sus siglas en inglés), como OpenAI, para contenido generado por IA. Más información en el SDK de IA.

import { Configuration, OpenAIApi } from 'openai-edge'
import { OpenAIStream, StreamingTextResponse } from 'ai'

export const runtime = 'edge'

const apiConfig = new Configuration({
  apiKey: process.env.OPENAI_API_KEY!,
})

const openai = new OpenAIApi(apiConfig)

export async function POST(req: Request) {
  // Extrae los `messages` del cuerpo de la solicitud
  const { messages } = await req.json()

  // Solicita a la API de OpenAI la respuesta basada en el prompt
  const response = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    stream: true,
    messages: messages,
    max_tokens: 500,
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 1,
    presence_penalty: 1,
  })

  // Convierte la respuesta en un flujo de texto amigable
  const stream = OpenAIStream(response)

  // Responde con el flujo
  return new StreamingTextResponse(stream)
}
import { Configuration, OpenAIApi } from 'openai-edge'
import { OpenAIStream, StreamingTextResponse } from 'ai'

export const runtime = 'edge'

const apiConfig = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
})

const openai = new OpenAIApi(apiConfig)

export async function POST(req) {
  // Extrae los `messages` del cuerpo de la solicitud
  const { messages } = await req.json()

  // Solicita a la API de OpenAI la respuesta basada en el prompt
  const response = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    stream: true,
    messages: messages,
    max_tokens: 500,
    temperature: 0.7,
    top_p: 1,
    frequency_penalty: 1,
    presence_penalty: 1,
  })

  // Convierte la respuesta en un flujo de texto amigable
  const stream = OpenAIStream(response)

  // Responde con el flujo
  return new StreamingTextResponse(stream)
}

Estas abstracciones utilizan las APIs Web para crear un flujo. También puede usar directamente las APIs Web subyacentes.

// 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>Uno</p>')
  await sleep(200)
  yield encoder.encode('<p>Dos</p>')
  await sleep(200)
  yield encoder.encode('<p>Tres</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>Uno</p>')
  await sleep(200)
  yield encoder.encode('<p>Dos</p>')
  await sleep(200)
  yield encoder.encode('<p>Tres</p>')
}

export async function GET() {
  const iterator = makeIterator()
  const stream = iteratorToStream(iterator)

  return new Response(stream)
}

Cuerpo de la Solicitud

Puede leer el cuerpo de la Request utilizando los métodos estándar de las APIs Web:

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 })
}

Cuerpo de la Solicitud FormData

Puede leer el FormData utilizando 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, puede usar zod-form-data para validar la solicitud y recuperar los datos en el formato que prefiera (por ejemplo, number).

CORS

Puede configurar los encabezados CORS en una Response utilizando los métodos estándar de las APIs Web:

export async function GET(request: Request) {
  return new Response('¡Hola, 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 async function GET(request) {
  return new Response('¡Hola, 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',
    },
  })
}

Entornos de Ejecución Edge y Node.js

Los Route Handlers tienen una API Web isomórfica para admitir tanto entornos de ejecución Edge como Node.js sin problemas, incluyendo soporte para streaming. Dado que los Route Handlers usan la misma configuración de segmento de ruta que las páginas y diseños, admiten funciones muy esperadas como Route Handlers regenerados estáticamente de propósito general.

Puede usar la opción de configuración de segmento runtime para especificar el entorno de ejecución:

export const runtime = 'edge' // 'nodejs' es el valor por defecto

Respuestas que no son UI

Puede usar Route Handlers para devolver contenido que no sea UI. Tenga en cuenta que sitemap.xml, robots.txt, app icons y open graph images tienen soporte incorporado.

export async function GET() {
  return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

<channel>
  <title>Documentación de Next.js</title>
  <link>https://nextjs.org/docs</link>
  <description>El Framework de React para la Web</description>
</channel>

</rss>`)
}
export async function GET() {
  return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

<channel>
  <title>Documentación de Next.js</title>
  <link>https://nextjs.org/docs</link>
  <description>El Framework de React para la Web</description>
</channel>

</rss>`)
}

Opciones de Configuración de Segmento

Los Route Handlers utilizan la misma configuración de segmento de ruta que las páginas y diseños.

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'

Consulte la referencia de API para más detalles.