Middleware
Middleware se ejecuta antes de cada solicitud coincidente, habilitando redirecciones, reescrituras, modificaciones de encabezados y verificaciones de autenticación en el borde.
Recipe
Tarjeta de referencia rápida — lista para copiar y pegar.
// middleware.ts (root de proyecto, mismo nivel que app/)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Redirigir, reescribir o modificar encabezados
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"],
};Cuándo usarlo: Guardias de autenticación, detección de idioma, pruebas A/B, redirecciones y cualquier lógica a nivel de solicitud que deba ejecutarse antes de renderizar.
Ejemplo de Funcionamiento
// middleware.ts — Guardia de autenticación con detección de idioma
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const protectedPaths = ["/dashboard", "/settings", "/billing"];
const defaultLocale = "en";
const supportedLocales = ["en", "fr", "de", "es"];
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. Detección de idioma — redirigir si no hay prefijo de idioma
const pathnameHasLocale = supportedLocales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (!pathnameHasLocale) {
const locale =
request.headers.get("accept-language")?.split(",")[0]?.split("-")[0] ?? defaultLocale;
const detectedLocale = supportedLocales.includes(locale) ? locale : defaultLocale;
return NextResponse.redirect(
new URL(`/${detectedLocale}${pathname}`, request.url)
);
}
// 2. Verificación de autenticación — redirigir usuarios no autenticados
const isProtected = protectedPaths.some((path) =>
pathname.includes(path)
);
const token = request.cookies.get("session-token")?.value;
if (isProtected && !token) {
const loginUrl = new URL("/en/login", request.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
// 3. Agregar encabezados personalizados
const response = NextResponse.next();
response.headers.set("x-pathname", pathname);
return response;
}
export const config = {
matcher: [
// Coincide todas las rutas excepto archivos estáticos e internos de Next.js
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};// middleware.ts — Mapa simple de redirecciones
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const redirects: Record<string, string> = {
"/old-blog": "/blog",
"/docs/v1": "/docs",
"/legacy-api": "/api/v2",
};
export function middleware(request: NextRequest) {
const redirect = redirects[request.nextUrl.pathname];
if (redirect) {
return NextResponse.redirect(new URL(redirect, request.url), 301);
}
return NextResponse.next();
}
export const config = {
matcher: ["/old-blog", "/docs/v1", "/legacy-api"],
};Análisis Profundo
Cómo Funciona
- Middleware se ejecuta en cada solicitud coincidente antes de que el manejador de rutas o página se renderice. Se ejecuta en Edge Runtime de forma predeterminada.
- Solo hay un archivo
middleware.tspor proyecto. Debe estar en la raíz (junto aapp/osrc/). No puedes tener archivos de middleware por ruta. - La configuración
matcherfiltra qué rutas activan middleware. Sin un matcher, middleware se ejecuta en cada solicitud incluyendo activos estáticos. NextResponse.next()continúa hacia la ruta coincidente. Puedes modificar encabezados de solicitud/respuesta mientras pasas.NextResponse.redirect(url)envía una respuesta de redirección (302 de forma predeterminada, pasa 301 para permanente).NextResponse.rewrite(url)sirve el contenido de una ruta diferente sin cambiar la URL del navegador.- Middleware se ejecuta en Edge Runtime. APIs de Node.js como
fs,pathy la mayoría de paquetes npm no están disponibles. Usa solo Web APIs. - Middleware puede leer y establecer cookies. Usa
request.cookies.get()yresponse.cookies.set()para gestión de sesiones. - Middleware no puede renderizar componentes React. Opera a nivel HTTP, antes de que ocurra cualquier renderización de React.
Variaciones
// Pruebas A/B basadas en reescritura
export function middleware(request: NextRequest) {
const bucket = request.cookies.get("ab-bucket")?.value;
const response = NextResponse.next();
if (!bucket) {
const newBucket = Math.random() > 0.5 ? "a" : "b";
response.cookies.set("ab-bucket", newBucket, { maxAge: 60 * 60 * 24 * 30 });
}
const currentBucket = bucket ?? "a";
if (request.nextUrl.pathname === "/pricing") {
return NextResponse.rewrite(
new URL(`/pricing/${currentBucket}`, request.url)
);
}
return response;
}// Limitación de velocidad con encabezados
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api/")) {
const ip = request.headers.get("x-forwarded-for") ?? "unknown";
// Verificar límite de velocidad (usando almacén externo como Upstash Redis)
// Esto es pseudocódigo — usa @upstash/ratelimit en producción
const response = NextResponse.next();
response.headers.set("X-RateLimit-Limit", "100");
return response;
}
return NextResponse.next();
}// Patrones de matcher
export const config = {
matcher: [
"/dashboard/:path*", // /dashboard y todas sus subrutas
"/api/:path*", // Todas las rutas API
"/blog/:slug", // Un segmento dinámico
"/((?!_next|static|api).*)", // Todo excepto _next, static, api
],
};Notas de TypeScript
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// Firma de la función middleware
export function middleware(request: NextRequest): NextResponse | Response | undefined;
// NextRequest extiende la Web Request API
// Propiedades clave:
// request.nextUrl — URL analizada con pathname, searchParams
// request.cookies — instancia de RequestCookies
// request.headers — objeto Headers estándar
// request.geo — { city, country, region } (solo Vercel)
// request.ip — IP del cliente (solo Vercel)
// Tipo de config
export const config: {
matcher: string | string[];
};Trampas
- Solo un archivo middleware por proyecto. Si necesitas lógica diferente para rutas diferentes, usa verificaciones condicionales en
request.nextUrl.pathnamedentro de una única función middleware. - Middleware se ejecuta en Edge Runtime. No puedes usar APIs específicas de Node.js. Bibliotecas como
bcrypt,fso controladores de bases de datos no funcionarán — usa alternativas compatibles con edge. - Middleware se ejecuta en activos estáticos también a menos que uses un
matcher. Siempre configura un matcher para evitar ejecución innecesaria. - Los bucles de redirección infinitos son fáciles de crear. Si redirige
/logina/en/loginy middleware también coincide con/en/login, obtienes un bucle. Excluye rutas de destino del matcher. NextResponse.next()no cierra el flujo. Otra lógica de middleware después aún se ejecuta. Retorna temprano si es necesario.- Las cookies configuradas en middleware están disponibles en Server Components vía
cookies()desdenext/headers. - Las reescrituras son invisibles para el cliente. La URL del navegador se mantiene igual, pero el servidor renderiza una ruta diferente. Esto puede confundir la navegación del lado del cliente si no se maneja con cuidado.
- Middleware no puede acceder al cuerpo de la solicitud. Opera solo en encabezados, cookies y URL.
Alternativas
| Enfoque | Cuándo Usar |
|---|---|
redirecciones de next.config.js | Reglas de redirección estáticas que no necesitan lógica en tiempo de ejecución |
reescrituras de next.config.js | Reglas de reescritura estáticas sin lógica condicional |
redirect() a nivel de ruta | Redirección dentro de un Server Component después de verificaciones de datos |
| Server Actions con autenticación | Proteger mutaciones en lugar de rutas |
| guardias de autenticación a nivel de layout | Verificar autenticación en un layout y redirigir |
Ejemplo del Mundo Real
De una aplicación SaaS Next.js 15 / React 19 en producción (SystemsArchitect.io).
// Ejemplo en producción: Middleware con reescrituras, redirecciones, actualización de sesión y protección de admin
// Archivo: middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { updateSession } from '@/utils/supabase/middleware';
const legacyRedirects: Record<string, string> = {
'/old-docs': '/docs',
'/blog/legacy-post': '/articles',
'/pricing-old': '/pricing',
};
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. Reescritura de docs: servir /docs/[section] desde una ruta interna diferente
if (pathname.startsWith('/docs/')) {
const url = request.nextUrl.clone();
url.pathname = `/docs-internal${pathname.replace('/docs', '')}`;
return NextResponse.rewrite(url);
}
// 2. Redirecciones heredadas (301 permanente)
const redirect = legacyRedirects[pathname];
if (redirect) {
return NextResponse.redirect(new URL(redirect, request.url), 301);
}
// 3. Actualización de sesión - mantener viva la sesión de autenticación de Supabase
const response = await updateSession(request);
// 4. Protección de rutas de admin
if (pathname.startsWith('/admin')) {
const session = request.cookies.get('sb-access-token')?.value;
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return response;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};Lo que esto demuestra en producción:
- El orden de ejecución importa. Las reescrituras se ejecutan primero, luego redirecciones heredadas, luego actualización de sesión, luego protección de admin. Si verificas autenticación antes de la reescritura, el usuario podría acceder a la ruta equivocada.
- El código de estado
301en redirecciones heredadas indica a los motores de búsqueda que el movimiento es permanente. Usar el302predeterminado preservaría la URL antigua en los índices de búsqueda. updateSession(request)es una utilidad de Supabase que actualiza la cookie de sesión de autenticación en cada solicitud. Esto previene que la sesión expire mientras el usuario está navegando activamente.- La expresión regular del matcher
/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)excluye activos estáticos e imágenes del procesamiento de middleware. Sin esto, middleware se ejecuta en cada solicitud de imagen y archivo CSS, agregando latencia innecesaria. - La protección de admin verifica una cookie en lugar de hacer una llamada a la base de datos. Middleware se ejecuta en Edge Runtime donde los controladores de bases de datos no están disponibles. Las verificaciones de autorización completas ocurren en Server Components o rutas API.
- Solo hay un archivo
middleware.tspor proyecto. Toda lógica específica de rutas debe manejarse con verificaciones condicionales enpathname.
FAQs
¿Dónde va el archivo middleware.ts?
En la raíz del proyecto, junto al directorio app/ o src/. Solo hay un archivo middleware.ts por proyecto. No puedes tener archivos de middleware por ruta.
¿Qué runtime usa middleware y cuáles son las limitaciones?
- Middleware se ejecuta en Edge Runtime
- APIs de Node.js como
fs,pathy la mayoría de paquetes npm no están disponibles - Solo puedes usar Web APIs
- No puedes acceder al cuerpo de la solicitud
- No puedes renderizar componentes React
¿Qué sucede si no configuras un matcher?
Middleware se ejecuta en cada solicitud, incluyendo activos estáticos como imágenes, CSS y archivos JavaScript. Siempre configura un matcher para evitar ejecución innecesaria.
Trampa: ¿Cómo evitas bucles de redirección infinitos en middleware?
Si redirige /login a /en/login y middleware también coincide con /en/login, obtienes un bucle infinito. Excluye rutas de destino del matcher o agrega verificaciones condicionales para saltar rutas ya procesadas.
¿Cuál es la diferencia entre NextResponse.redirect y NextResponse.rewrite?
redirect()envía una respuesta de redirección (302 de forma predeterminada, 301 para permanente) y cambia la URL del navegadorrewrite()sirve el contenido de una ruta diferente sin cambiar la URL del navegador
¿Cómo lees y estableces cookies en middleware?
export function middleware(request: NextRequest) {
// Leer una cookie
const token = request.cookies.get("session")?.value;
// Establecer una cookie en la respuesta
const response = NextResponse.next();
response.cookies.set("visited", "true", { maxAge: 3600 });
return response;
}¿Las cookies configuradas en middleware están disponibles en Server Components?
Sí. Las cookies configuradas en middleware están disponibles vía cookies() desde next/headers en Server Components.
¿Cómo manejas lógica diferente para rutas diferentes en un único archivo middleware?
Usa verificaciones condicionales en request.nextUrl.pathname dentro de la función middleware, ya que solo puedes tener un archivo middleware.
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname.startsWith("/api/")) { /* Lógica de API */ }
if (pathname.startsWith("/admin")) { /* Lógica de admin */ }
return NextResponse.next();
}¿Cuál es la firma de TypeScript para la función middleware?
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(
request: NextRequest
): NextResponse | Response | undefined;
export const config: {
matcher: string | string[];
};¿Qué propiedades están disponibles en NextRequest?
request.nextUrl-- URL analizada con pathname y searchParamsrequest.cookies-- instancia de RequestCookiesrequest.headers-- objeto Headers estándarrequest.geo-- ciudad, país, región (solo Vercel)request.ip-- IP del cliente (solo Vercel)
Trampa: ¿NextResponse.next() cierra el flujo de la función middleware?
No. El código después de NextResponse.next() aún se ejecuta. Si deseas detener el procesamiento, debes explícitamente return la respuesta.
¿Cuándo deberías usar redirecciones middleware vs. redirecciones de next.config.js?
- Usa redirecciones de
next.config.jspara reglas estáticas que no necesitan lógica en tiempo de ejecución - Usa redirecciones de middleware cuando necesitas lógica condicional basada en cookies, encabezados u otros datos de solicitud
Relacionado
- App Router Basics — descripción de convenciones de archivos
- Navigation —
redirect()y navegación programática - Route Groups — organizando rutas que middleware protege
- Dynamic Routes — patrones de matcher para segmentos dinámicos