Middleware
Middleware se ejecuta antes de cada solicitud coincidente, habilitando redirecciones, reescrituras, modificaciones de encabezados y verificaciones de autenticación en el borde.
Busca en todas las páginas de la documentación
Middleware se ejecuta antes de cada solicitud coincidente, habilitando redirecciones, reescrituras, modificaciones de encabezados y verificaciones de autenticación en el borde.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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.
// 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"],
};middleware.ts por proyecto. Debe estar en la raíz (junto a app/ o src/). No puedes tener archivos de middleware por ruta.matcher filtra 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.fs, path y la mayoría de paquetes npm no están disponibles. Usa solo Web APIs.request.cookies.get() y response.cookies.set() para gestión de sesiones.// 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
],
};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[];
};request.nextUrl.pathname dentro de una única función middleware.bcrypt, fs o controladores de bases de datos no funcionarán - usa alternativas compatibles con edge.matcher. Siempre configura un matcher para evitar ejecución innecesaria./login a /en/login y 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.cookies() desde next/headers.| 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 |
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:
301 en redirecciones heredadas indica a los motores de búsqueda que el movimiento es permanente. Usar el 302 predeterminado 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./((?!_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.middleware.ts por proyecto. Toda lógica específica de rutas debe manejarse con verificaciones condicionales en pathname.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.
fs, path y la mayoría de paquetes npm no están disponiblesMiddleware 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.
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.
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 navegadorexport 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;
}Sí. Las cookies configuradas en middleware están disponibles vía cookies() desde next/headers en Server Components.
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();
}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[];
};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)No. El código después de NextResponse.next() aún se ejecuta. Si deseas detener el procesamiento, debes explícitamente return la respuesta.
next.config.js para reglas estáticas que no necesitan lógica en tiempo de ejecuciónredirect() y navegación programáticaRevisado por Chris St. John·Última actualización: 10 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥