Middleware
Middleware te permite ejecutar código antes de que se complete una solicitud. Luego, basado en la solicitud entrante, puedes modificar la respuesta reescribiendo, redireccionando, modificando los encabezados de solicitud o respuesta, o respondiendo directamente.
Middleware se ejecuta antes de que el contenido en caché y las rutas sean coincidentes. Consulta Coincidencia de Rutas para más detalles.
Casos de Uso
Integrar Middleware en tu aplicación puede llevar a mejoras significativas en rendimiento, seguridad y experiencia de usuario. Algunos escenarios comunes donde Middleware es particularmente efectivo incluyen:
- Autenticación y Autorización: Verificar la identidad del usuario y comprobar cookies de sesión antes de conceder acceso a páginas específicas o rutas de API.
- Redirecciones del Lado del Servidor: Redirigir usuarios a nivel de servidor basado en ciertas condiciones (ej. idioma, rol de usuario).
- Reescribir Rutas: Soporte para pruebas A/B, lanzamientos de características o rutas heredadas reescribiendo dinámicamente rutas a API o páginas basado en propiedades de la solicitud.
- Detección de Bots: Proteger tus recursos detectando y bloqueando tráfico de bots.
- Registro y Analítica: Capturar y analizar datos de solicitudes para obtener insights antes del procesamiento por la página o API.
- Feature Flagging: Habilitar o deshabilitar características dinámicamente para lanzamientos de funciones o pruebas.
Reconocer situaciones donde middleware puede no ser el enfoque óptimo es igualmente crucial. Aquí hay algunos escenarios a tener en cuenta:
- Obtención y Manipulación Compleja de Datos: Middleware no está diseñado para obtener o manipular datos directamente, esto debe hacerse dentro de Route Handlers o utilidades del lado del servidor.
- Tareas Computacionales Pesadas: Middleware debe ser liviano y responder rápidamente o puede causar retrasos en la carga de la página. Tareas computacionales pesadas o procesos largos deben hacerse dentro de Route Handlers dedicados.
- Gestión Extensa de Sesiones: Si bien Middleware puede manejar tareas básicas de sesión, la gestión extensa de sesiones debe manejarse mediante servicios de autenticación dedicados o dentro de Route Handlers.
- Operaciones Directas de Base de Datos: No se recomienda realizar operaciones directas de base de datos dentro de Middleware. Las interacciones con la base de datos deben hacerse dentro de Route Handlers o utilidades del lado del servidor.
Convención
Usa el archivo middleware.ts
(o .js
) en la raíz de tu proyecto para definir Middleware. Por ejemplo, al mismo nivel que pages
o app
, o dentro de src
si aplica.
Nota: Aunque solo se admite un archivo
middleware.ts
por proyecto, aún puedes organizar tu lógica de middleware de forma modular. Separa las funcionalidades de middleware en archivos.ts
o.js
separados e impórtalos en tu archivo principalmiddleware.ts
. Esto permite una gestión más limpia de middleware específico de rutas, agregado enmiddleware.ts
para control centralizado. Al imponer un único archivo de middleware, simplifica la configuración, previene conflictos potenciales y optimiza el rendimiento evitando múltiples capas de middleware.
Ejemplo
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// Esta función puede marcarse como `async` si se usa `await` dentro
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// Consulta "Coincidencia de Rutas" para más información
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'
// Esta función puede marcarse como `async` si se usa `await` dentro
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}
// Consulta "Coincidencia de Rutas" para más información
export const config = {
matcher: '/about/:path*',
}
Coincidencia de Rutas
Middleware se invocará para cada ruta en tu proyecto. Dado esto, es crucial usar matchers para apuntar o excluir rutas específicas con precisión. El siguiente es el orden de ejecución:
headers
denext.config.js
redirects
denext.config.js
- Middleware (
rewrites
,redirects
, etc.) beforeFiles
(rewrites
) denext.config.js
- Rutas del sistema de archivos (
public/
,_next/static/
,pages/
,app/
, etc.) afterFiles
(rewrites
) denext.config.js
- Rutas Dinámicas (
/blog/[slug]
) fallback
(rewrites
) denext.config.js
Hay dos formas de definir en qué rutas se ejecutará Middleware:
Matcher
matcher
te permite filtrar Middleware para que se ejecute en rutas específicas.
export const config = {
matcher: '/about/:path*',
}
Puedes coincidir con una sola ruta o múltiples rutas con sintaxis de array:
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
La configuración matcher
permite regex completo, por lo que coincidencias como lookaheads negativos o coincidencia de caracteres son soportadas. Un ejemplo de lookahead negativo para coincidir con todo excepto rutas específicas se puede ver aquí:
export const config = {
matcher: [
/*
* Coincide con todas las rutas de solicitud excepto las que comienzan con:
* - api (rutas de API)
* - _next/static (archivos estáticos)
* - _next/image (archivos de optimización de imágenes)
* - favicon.ico (archivo de favicon)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
También puedes omitir Middleware para ciertas solicitudes usando los arrays missing
o has
, o una combinación de ambos:
export const config = {
matcher: [
/*
* Coincide con todas las rutas de solicitud excepto las que comienzan con:
* - api (rutas de API)
* - _next/static (archivos estáticos)
* - _next/image (archivos de optimización de imágenes)
* - favicon.ico (archivo de favicon)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
Bueno saber: Los valores de
matcher
deben ser constantes para que puedan analizarse estáticamente en tiempo de compilación. Valores dinámicos como variables serán ignorados.
Matchers configurados:
- DEBEN comenzar con
/
- Pueden incluir parámetros nombrados:
/about/:path
coincide con/about/a
y/about/b
pero no con/about/a/c
- Pueden tener modificadores en parámetros nombrados (comenzando con
:
):/about/:path*
coincide con/about/a/b/c
porque*
es cero o más.?
es cero o uno y+
uno o más - Pueden usar expresiones regulares encerradas en paréntesis:
/about/(.*)
es lo mismo que/about/:path*
Lee más detalles en la documentación de path-to-regexp.
Bueno saber: Por compatibilidad con versiones anteriores, Next.js siempre considera
/public
como/public/index
. Por lo tanto, un matcher de/public/:path
coincidirá.
Declaraciones Condicionales
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
La API NextResponse
te permite:
redirect
la solicitud entrante a una URL diferenterewrite
la respuesta mostrando una URL dada- Establecer encabezados de solicitud para Rutas de API,
getServerSideProps
, y destinos derewrite
- Establecer cookies de respuesta
- Establecer encabezados de respuesta
Para producir una respuesta desde Middleware, puedes:
rewrite
a una ruta (Página o Edge API Route) que produzca una respuesta- devolver un
NextResponse
directamente. Consulta Produciendo una Respuesta
Usando Cookies
Las cookies son encabezados regulares. En una Request
, se almacenan en el encabezado Cookie
. En una Response
están en el encabezado Set-Cookie
. Next.js proporciona una forma conveniente de acceder y manipular estas cookies a través de la extensión cookies
en NextRequest
y NextResponse
.
- Para solicitudes entrantes,
cookies
viene con los siguientes métodos:get
,getAll
,set
, ydelete
cookies. Puedes verificar la existencia de una cookie conhas
o eliminar todas las cookies conclear
. - Para respuestas salientes,
cookies
tiene los siguientes métodosget
,getAll
,set
, ydelete
.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Asume que existe un encabezado "Cookie:nextjs=fast" en la solicitud entrante
// Obteniendo cookies de la solicitud usando la API `RequestCookies`
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Estableciendo cookies en la respuesta usando la API `ResponseCookies`
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// La respuesta saliente tendrá un encabezado `Set-Cookie:vercel=fast;path=/`.
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// Asume que existe un encabezado "Cookie:nextjs=fast" en la solicitud entrante
// Obteniendo cookies de la solicitud usando la API `RequestCookies`
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Estableciendo cookies en la respuesta usando la API `ResponseCookies`
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// La respuesta saliente tendrá un encabezado `Set-Cookie:vercel=fast;path=/test`.
return response
}
Estableciendo Encabezados
Puedes establecer encabezados de solicitud y respuesta usando la API NextResponse
(establecer encabezados de solicitud está disponible desde Next.js v13.0.0).
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clona los encabezados de solicitud y establece un nuevo encabezado `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// También puedes establecer encabezados de solicitud en NextResponse.rewrite
const response = NextResponse.next({
request: {
// Nuevos encabezados de solicitud
headers: requestHeaders,
},
})
// Establece un nuevo encabezado de respuesta `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// Clona los encabezados de solicitud y establece un nuevo encabezado `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// También puedes establecer encabezados de solicitud en NextResponse.rewrite
const response = NextResponse.next({
request: {
// Nuevos encabezados de solicitud
headers: requestHeaders,
},
})
// Establece un nuevo encabezado de respuesta `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
Bueno saber: Evita establecer encabezados grandes ya que podría causar un error 431 Request Header Fields Too Large dependiendo de la configuración de tu servidor web backend.
CORS
Puede configurar los encabezados CORS en Middleware para permitir solicitudes de origen cruzado (cross-origin), incluyendo solicitudes simples y preflight.
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// Verificar el origen de la solicitud
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Manejar solicitudes preflight
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Manejar solicitudes simples
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
import { NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request) {
// Verificar el origen de la solicitud
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Manejar solicitudes preflight
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Manejar solicitudes simples
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
Producir una respuesta
Puede responder directamente desde Middleware devolviendo una instancia de Response
o NextResponse
. (Disponible desde Next.js v13.1.0)
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limitar el middleware a rutas que comienzan con `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Llamar a nuestra función de autenticación para verificar la solicitud
if (!isAuthenticated(request)) {
// Responder con JSON indicando un mensaje de error
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
import { isAuthenticated } from '@lib/auth'
// Limitar el middleware a rutas que comienzan con `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// Llamar a nuestra función de autenticación para verificar la solicitud
if (!isAuthenticated(request)) {
// Responder con JSON indicando un mensaje de error
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
y NextFetchEvent
El objeto NextFetchEvent
extiende el objeto nativo FetchEvent
e incluye el método waitUntil()
.
El método waitUntil()
toma una promesa como argumento y extiende el tiempo de vida del Middleware hasta que la promesa se resuelva. Esto es útil para realizar trabajos en segundo plano.
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
Banderas avanzadas de Middleware
En la versión v13.1
de Next.js se introdujeron dos banderas adicionales para middleware: skipMiddlewareUrlNormalize
y skipTrailingSlashRedirect
para manejar casos de uso avanzados.
skipTrailingSlashRedirect
desactiva las redirecciones de Next.js para agregar o eliminar barras diagonales finales. Esto permite un manejo personalizado dentro del middleware para mantener la barra diagonal final en algunas rutas pero no en otras, lo que puede facilitar migraciones incrementales.
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// aplicar manejo de barra diagonal final
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}
skipMiddlewareUrlNormalize
permite desactivar la normalización de URL en Next.js para manejar visitas directas y transiciones del cliente de la misma manera. En algunos casos avanzados, esta opción proporciona control total utilizando la URL original.
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// con la bandera esto sería /_next/data/build-id/hello.json
// sin la bandera esto se normalizaría a /hello
}
Entorno de ejecución (Runtime)
Middleware actualmente solo soporta el Edge runtime. No se puede utilizar el entorno de ejecución Node.js.
Historial de versiones
Versión | Cambios |
---|---|
v13.1.0 | Se agregaron banderas avanzadas para Middleware |
v13.0.0 | Middleware puede modificar encabezados de solicitud, encabezados de respuesta y enviar respuestas |
v12.2.0 | Middleware es estable, consulte la guía de actualización |
v12.0.9 | Se aplican URLs absolutas en Edge Runtime (PR) |
v12.0.0 | Se agregó Middleware (Beta) |