Conocimientos previos

¿Qué es el middleware? Guía para principiantes

Entiende el middleware desde primeros principios, por qué los SaaS lo usan y cómo funciona nuestro middleware en Next.js con i18n y request IDs — además de cómo personalizarlo.

Middleware en una frase

El middleware es código que se ejecuta entre una solicitud entrante y tu handler final, para inspeccionar, redirigir o añadir headers antes de que corra la página o API.


Middleware 101 (perspectiva de principiante)

Imagina una solicitud como un auto en la carretera. El middleware es el punto de control: todas las solicitudes pasan por ahí. En ese punto puedes leer la solicitud, tomar decisiones y opcionalmente cambiar la respuesta (o incluso detenerla antes).

Usos comunes en apps web:

  • Puertas de autenticación: comprobar si el usuario está logueado, si no, redirigir.
  • Internacionalización (i18n): enviar al usuario al locale correcto según URL o preferencias.
  • Logging y trazas: adjuntar un request_id para correlacionar logs de punta a punta.
  • Feature flags / A/B testing: asignar usuarios a experimentos temprano.
  • Seguridad y rate limiting: bloquear tráfico abusivo o reforzar headers.

En Next.js, el middleware vive en un archivo especial middleware.ts y corre en el edge (muy temprano y muy rápido) para las rutas que coinciden. Puede reescribir/redirigir solicitudes, leer o setear headers y cortar la respuesta antes de que se ejecuten los handlers.


Por qué ayuda (y cuándo lo necesitas)

El middleware centraliza preocupaciones transversales (lógica que aplica a muchas rutas), así no repites lo mismo en cada página o API:

  • Un solo lugar para reglas: menos chances de olvidar auth o locale.
  • Observabilidad consistente: todas las requests tienen el mismo request_id y headers.
  • Decisiones rápidas: redirige o niega temprano, ahorrando trabajo al servidor.
  • Handlers más limpios: la lógica de negocio queda en páginas/APIs, no en el plumbing.

Escenarios SaaS donde brilla:

  • Apps localizadas que deben mantener URLs /:locale/... consistentes.
  • Apps que dependen de IDs de correlación por request para logs y trazas.
  • Gatekeeping de funciones de pago o zonas admin (con manejo cuidadoso de cookies).
  • Experimentación (A/B), geo routing y normalización de headers.

Nuestro middleware: qué hace hoy

Incluimos un default mínimo y seguro en src/middleware.ts que combina el routing i18n de next-intl con propagación ligera de request IDs.

Comportamientos clave:

  • Routing de locales: delega a next-intl usando nuestra configuración (src/i18n/routing.ts, src/i18n/locale.ts). Mantiene URLs como /en/..., /fr/... consistentes y permite detección opcional de locale.
  • Header de request ID: asegura que toda respuesta lleve x-request-id para correlación de logs. Si la request ya trae uno, lo reutilizamos; si no, generamos un UUID.
  • Matcher conservador: corre en páginas localizadas y rutas generales; salta assets, internals de Next, rutas API y admin.

Código actual (recortado para claridad):

// src/middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest } from 'next/server';
import { routing } from '@/i18n/routing';

const intlMiddleware = createMiddleware(routing);

export default function middleware(request: NextRequest) {
  const existing = request.headers.get('x-request-id');
  const requestId = existing || crypto.randomUUID();

  const res = intlMiddleware(request);
  // Solo visibilidad; no mutar cookies/headers de la request.
  res.headers.set('x-request-id', requestId);
  return res;
}

export const config = {
  matcher: [
    '/',
    '/(en|en-US|zh|zh-CN|zh-TW|zh-HK|zh-MO|ja|ko|ru|fr|de|ar|es|it)/:path*',
    '/((?!api|_next|_vercel|admin|.*\\..*).*)',
  ],
};

Configuración de locales aquí:

// src/i18n/routing.ts
import { defaultLocale, localeDetection, localePrefix, locales } from './locale';
import { defineRouting } from 'next-intl/routing';

export const routing = defineRouting({ locales, defaultLocale, localePrefix, localeDetection });

Locales declarados aquí (edita para añadir/quitar):

// src/i18n/locale.ts
export const locales = ['en', 'zh', 'es', 'fr', 'ja'];
export const defaultLocale = 'en';
export const localePrefix = 'always';
export const localeDetection = process.env.NEXT_PUBLIC_LOCALE_DETECTION === 'true';

Notas:

  • Evitamos cambiar headers/cookies de la request en middleware para mantener Better Auth estable. Solo seteamos un header de respuesta.
  • El matcher excluye api, _next, assets y admin por defecto. Ajusta si necesitas middleware allí.

Cómo personalizar (con seguridad)

Antes de personalizar, valida que la lógica realmente pertenece al middleware (transversal, decisión temprana) y no a una ruta específica. Cuando sí aplica, usa patrones como estos:

Añadir headers personalizados

export default function middleware(request: NextRequest) {
  const res = intlMiddleware(request);
  res.headers.set('x-feature-flag', 'on');
  return res;
}

Redirigir o reescribir rutas

import { NextResponse } from 'next/server';

export default function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  if (pathname === '/old') {
    return NextResponse.redirect(new URL('/new', request.url));
  }
  return intlMiddleware(request);
}

Incluir/excluir rutas con matcher

export const config = {
  matcher: [
    '/',
    // Volver a incluir admin quitándolo del negative lookahead
    // o haciendo match explícito:
    '/admin/:path*',
    '/(en|fr|ja|zh|es)/:path*',
    '/((?!api|_next|_vercel|.*\\..*).*)',
  ],
};

Usar request IDs dentro de handlers

// En un handler de Node
import { headers } from 'next/headers';

export async function GET() {
  const h = headers();
  const request_id = h.get('x-request-id');
  // adjunta a logs, trazas de DB, etc.
  return new Response('ok');
}

Ajustes de internacionalización

  • Cambia NEXT_PUBLIC_LOCALE_DETECTION a true para activar auto detección.
  • Actualiza src/i18n/locale.ts para añadir/quitar locales y nombres.
  • Mantén content/docs, messages/ y src/i18n sincronizados al agregar un locale.

Auth y flujos sensibles

  • Si agregas gatekeeping de auth en middleware, evita mutar cookies; prefiere redirecciones y checks de solo lectura.
  • Verifica que las rutas API protegidas también se validen en el handler — middleware es una comodidad, no tu único guardia.

Cuándo NO usar middleware

  • Lógica específica de una ruta que no aplica de forma amplia.
  • Trabajo async pesado (llamadas a DB) que debe ir en el handler.
  • Transformar cuerpos grandes de requests (middleware no puede leer el body).

Mantén el middleware rápido y enfocado. Si crece demasiado, mueve la lógica especializada a servicios y llámala desde los handlers.


TL;DR

  • Middleware corre temprano y de forma central — ideal para i18n, headers, redirects y guardas ligeras.
  • Nuestro middleware por defecto maneja locale routing y setea x-request-id para trazabilidad.
  • Personaliza con config.matcher, headers de respuesta y redirects seguros; evita tocar cookies de auth.
  • Manténlo pequeño, rápido y consistente entre locales.