Middleware

middleware.ts: redirecciones, reescrituras, cabeceras y cookies antes de cada petición, con config.matcher.

El middleware ejecuta código antes de que se complete una solicitud. Puede redirigir, reescribir, establecer cabeceras y cookies, o devolver una respuesta por sí mismo.

Convención

Crea middleware.ts en la raíz del proyecto (o en src/), junto a app/. Exporta una función middleware (o una exportación predeterminada) y un config opcional.

middleware.tsts
import { NextResponse, type NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest) {  if (!request.cookies.has("session")) {    return NextResponse.redirect(new URL("/login", request.url));  }  return NextResponse.next();}export const config = {  matcher: ["/dashboard/:path*", "/api/:path*"],};

El middleware se ejecuta antes que los manejadores de ruta, los archivos estáticos y las páginas de consola, en text dev, text start y cada adaptador de despliegue de servidor. Usa las API web Request y Response, por lo que el mismo archivo se ejecuta en Node, Bun, Vercel Edge y Cloudflare Workers.

Coincidencia de rutas

Sin un config.matcher, el middleware se ejecuta para cada solicitud. Los patrones de coincidencia usan la misma sintaxis que Next.js:

PatrónCoincidencias
/about/about y /about/
/blog/:slug/blog/hello
/docs/:path*/docs, /docs/a/b
/a/:rest+/a/b, pero no /a
/user/:id(\\d+)/user/42, pero no /user/ada
/((?!api/).*)Todo excepto /api/*

Añade condiciones con has y missing (cabecera, cookie, consulta o host):

middleware.tsts
export const config = {  matcher: [{ source: "/beta/:path*", has: [{ type: "cookie", key: "beta", value: "1" }] }],};

Los patrones se aplican a la ruta de acceso sin basePath. En text dev, las solicitudes de módulos del propio Vite (/@vite/*, archivos fuente) nunca ejecutan el middleware.

Cada host determina una ruta de acceso canónica antes de ejecutar el middleware y la usa para la coincidencia de patrones, request.nextUrl y la búsqueda de rutas, manejadores y archivos. Se decodifican las secuencias de escape con porcentaje y se vuelve a codificar la ruta tal como la escribe new URL(), se resuelven los segmentos . y .., y se eliminan las barras repetidas y finales. /ad%6din//users/ llega al middleware como /admin/users, mientras que /café se mantiene como /caf%C3%A9. Un patrón escrito con una barra final, como /admin/, coincide con /admin. Las solicitudes cuya ruta contiene una barra o barra inversa codificada (%2F, %5C), un carácter de control o una codificación con porcentaje no válida reciben un 400. Las rutas distinguen entre mayúsculas y minúsculas: /Admin no coincide con /admin, y los archivos estáticos solo se sirven con su nombre exacto.

Incluye en los patrones las URL en las que se sirve un archivo, no solo su nombre. Cloudflare Pages también sirve report.html en /report, por lo que un patrón para /report.html no protege /report. Enumera ambas, matcher: ["/report", "/report.html"], o guarda los archivos protegidos en un directorio y usa un patrón que lo cubra, como /reports/:path* para los archivos de public/reports/.

NextResponse

APIEfecto
NextResponse.next()Continuar a la ruta
NextResponse.redirect(url, status?)307 de forma predeterminada; url debe ser absoluta
NextResponse.rewrite(url)Servir otra ruta; los orígenes externos se sirven mediante un proxy
NextResponse.json(body, init?)Responder con JSON
new Response(...)Responder con cualquier contenido

No devuelvas nada para continuar sin cambios. next() y rewrite() aceptan { status } para cambiar el estado de la respuesta final. Solo las respuestas creadas por NextResponse.next() / NextResponse.rewrite() controlan el enrutamiento: cualquier otra Response, incluida una devuelta por fetch(), se envía al cliente tal cual, con sus cabeceras x-middleware-* eliminadas.

request.nextUrl excluye basePath de pathname, mientras que sus valores href y toString() lo incluyen. Para permanecer dentro de la app, clónalo: const url = request.nextUrl.clone(); url.pathname = "/login"; return NextResponse.redirect(url);.

middleware.tsts
export function middleware(request: NextRequest) {  if (request.nextUrl.pathname === "/status") {    return NextResponse.rewrite(new URL("/api/health", request.url));  }}

Cabeceras

Pasa request.headers para cambiar lo que reciben los manejadores posteriores, y headers para añadir cabeceras de respuesta:

middleware.tsts
export function middleware(request: NextRequest) {  const headers = new Headers(request.headers);  headers.set("x-user", "ada");  const response = NextResponse.next({ request: { headers } });  response.headers.set("x-frame", "▼");  return response;}

Las cabeceras de respuesta del middleware se añaden a la respuesta final, salvo que el manejador de ruta establezca la misma cabecera.

Cookies

request.cookies lee la cabecera Cookie; response.cookies escribe Set-Cookie.

middleware.tsts
export function middleware(request: NextRequest) {  const theme = request.cookies.get("theme")?.value ?? "geistDark";  const response = NextResponse.next();  response.cookies.set("theme", theme, { httpOnly: true, sameSite: "lax", maxAge: 60 * 60 * 24 });  return response;}

Las cookies establecidas en una respuesta de next() o rewrite() también son visibles para el manejador de ruta en la misma solicitud.

Trabajo en segundo plano

El segundo argumento tiene waitUntil(), que mantiene activos los entornos de ejecución sin servidor después de enviar la respuesta:

middleware.tsts
import type { NextFetchEvent, NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest, event: NextFetchEvent) {  event.waitUntil(fetch("https://analytics.example.com", { method: "POST", body: request.url }));}

Notas

  • Las páginas renderizadas en el cliente se renderizan en el navegador, por lo que una reescritura a otra página cambia el archivo que se sirve, no la ruta que renderiza la app de consola. Las reescrituras a manejadores de ruta, archivos estáticos y orígenes externos se comportan como se espera.
  • El middleware puede leer el cuerpo de la solicitud; los manejadores de ruta siguen recibiéndolo. El cuerpo de las solicitudes excluidas por un patrón nunca se modifica ni se lee.
  • request.url siempre tiene el origen del servidor: los destinos de solicitud como //other.host/x se normalizan a una sola barra inicial.
  • El adaptador estático no puede ejecutar middleware.
Editar esta página en GitHub