Next.js
Proxy: reemplazo actual de Middleware
Explica proxy.ts en Next.js 16, sus matchers, redirects, rewrites, headers, cookies, multi-tenancy y límites como frontera previa al routing.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Explica proxy.ts en Next.js 16, sus matchers, redirects, rewrites, headers, cookies, multi-tenancy y límites como frontera previa al routing.
Desde Next.js 16, proxy.ts reemplaza la convención middleware.ts. El nuevo nombre enfatiza que es una frontera de red previa al routing, no una capa global donde deba vivir la lógica de negocio. Úsalo como último recurso para decisiones tempranas de URL, headers o redirección.
proxy.ts vive en la raíz o dentro de src, al mismo nivel que app o pages:
// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
return NextResponse.next();
}Se ejecuta antes de que Next.js complete el routing:
request
→ config headers/redirects
→ Proxy
→ rewrites/filesystem routes
→ page/handlerPuede modificar el destino o la request, pero no debe sustituir la autorización y datos de la route final.
“Middleware” invitaba a pensar en Express:
request → middleware 1 → middleware 2 → controllerNext.js solo admite una convención Proxy por proyecto y puede desplegarla como frontera separada. El nombre comunica:
npx @next/codemod@canary middleware-to-proxy .Transforma:
// middleware.ts
export function middleware() {}En:
// proxy.ts
export function proxy() {}Revisa imports, tests y documentación. No basta renombrar si dependías de runtime o flags antiguos.
En Next.js 16 Proxy utiliza Node.js runtime por defecto y no acepta exportar runtime en su config. Intentarlo produce error.
Esto amplía compatibilidad frente al antiguo Edge-only, pero sigue sin garantizar persistencia de memoria o acceso a infraestructura local bajo cualquier plataforma.
No dependas de globals compartidos con el servidor de render.
Sin matcher, Proxy puede evaluarse para muchas requests. Limítalo:
export const config = {
matcher: ["/dashboard/:path*", "/account/:path*"],
};Valores deben ser constantes analizables en build.
export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|.*\\.(?:png|jpg|svg)$).*)",
],
};Ajusta a tu proyecto. Una expresión demasiado amplia puede interceptar RSC prefetch, imágenes, metadata o APIs.
No copies regex sin probarla.
export const config = {
matcher: [
{
source: "/dashboard/:path*",
missing: [{ type: "header", key: "purpose", value: "prefetch" }],
},
],
};Permite excluir prefetch u orientar por cookie/header/query.
Los matchers son optimización y routing, no autorización.
export function proxy(request: NextRequest) {
if (request.nextUrl.pathname === "/old") {
return NextResponse.redirect(new URL("/new", request.url));
}
return NextResponse.next();
}Para redirects estáticos conocidos, next.config suele ser más simple y ocurre antes de Proxy.
Usa Proxy cuando la decisión depende de request.
export function proxy(request: NextRequest) {
const hostname = request.headers.get("host");
const tenant = resolveTenantFromHost(hostname);
if (!tenant) return NextResponse.next();
const url = request.nextUrl.clone();
url.pathname = `/_tenants/${tenant.slug}${url.pathname}`;
return NextResponse.rewrite(url);
}La URL visible permanece. La route interna recibe el path reescrito.
export function proxy(request: NextRequest) {
const headers = new Headers(request.headers);
headers.set("x-request-id", crypto.randomUUID());
headers.delete("x-internal-user");
return NextResponse.next({ request: { headers } });
}Diferencia request headers enviados hacia la app de response headers enviados al navegador.
Sobrescribe headers internos para evitar spoofing.
Headers grandes pueden producir 431.
const response = NextResponse.next();
response.cookies.set("locale", "es", {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
});
return response;Proxy puede leer/escribir cookies antes del render. No guardes datos sensibles sin protección y límites de tamaño.
Una cookie de sesión aparente no prueba que sea válida; la route final verifica firma/estado.
export function proxy(request: NextRequest) {
const hasSessionCookie = request.cookies.has("session");
if (!hasSessionCookie) {
const login = new URL("/login", request.url);
login.searchParams.set("next", safePath(request.nextUrl));
return NextResponse.redirect(login);
}
return NextResponse.next();
}Esto mejora UX y evita render innecesario.
No consulta permisos de cada recurso ni reemplaza requireSession() en pages/Actions/Handlers.
Server Actions son POST a la route donde se usan. Si el matcher excluye esa route o cambias la ubicación de la Action, Proxy puede dejar de cubrirla.
Por eso:
Proxy auth hint
≠ Action authSiempre valida dentro de la Action.
Proxy puede detectar idioma y redirect/rewrite:
Accept-Language + cookie + URL
→ choose locale
→ /es/productsOrden recomendado:
Evita redirect loops y define canonical/hreflang.
Puedes asignar una variante y pasarla por cookie/header/rewrite:
stable user key
→ deterministic bucket
→ response cookie
→ route reads variantNo uses random por request; produciría experiencia inestable y contaminaría métricas.
La cache debe variar por variante o la página podría mezclar grupos.
acme.example.com/dashboard
→ rewrite /_sites/acme/dashboardValida host contra dominios permitidos. Host puede ser manipulable en algunos entornos; confía en headers normalizados por la plataforma.
Autorización de tenant vuelve a comprobarse con sesión y DB.
Proxy puede añadir CORS común para /api, pero las reglas por endpoint suelen ser más claras en Handlers/config.
No reflejes cualquier Origin; usa allowlist y Vary: Origin.
CORS no protege llamadas servidor-a-servidor.
export function proxy(request: NextRequest) {
if (isBlocked(request)) {
return Response.json(
{ error: "blocked" },
{ status: 403 },
);
}
}Útil para filtros simples. Para páginas de error ricas, rewrite a una route suele integrar mejor metadata y UI.
No hagas llamadas lentas antes de responder a toda request.
Proxy puede extender trabajo no bloqueante:
export function proxy(request: NextRequest, event: NextFetchEvent) {
event.waitUntil(sendAnalytics(request));
return NextResponse.next();
}No garantiza durabilidad de una operación crítica. Usa logs/plataforma/cola para auditoría o negocio.
headers config
redirects config
Proxy
beforeFiles rewrites
filesystem
...Una rewrite de Proxy precede a las de filesystem. Prueba combinaciones para evitar que una regla opaque otra.
Navegaciones generan requests especiales. Una Proxy que añade redirect/cookie o logging pesado a cada prefetch puede:
Usa missing/has, headers de prefetch y pruebas en producción.
Evita log por cada asset. Registra:
Proxy puede estar en una región distinta al render; correlaciona IDs.
Proxy afecta cada request coincidente. Objetivo:
Una consulta de sesión completa en Proxy y otra en DAL duplica trabajo. Proxy puede verificar un token liviano o solo presencia; la DAL hace la comprobación autoritativa.
Alternativas:
next.config redirects/rewrites/headers.Next.js ofrece helpers experimentales de unit testing para Proxy. Complementa con integración/E2E:
Aumenta TTFB y coste.
Actions quedan expuestas.
Intercepta assets/prefetch.
Permite spoofing.
No es compartido de forma confiable.
Variante cambia por request.
Rewrite conserva URL; redirect cambia navegación.
Runtime, nombre y flags pueden estar obsoletos.
Redirects, rewrites y headers compara reglas estáticas, Proxy y respuestas runtime para controlar el borde HTTP.