Middleware

Middleware se ejecuta antes de que una request llegue a tu pagina o API. Sirve para autenticacion, redirects, agregar headers y mas.

Crear middleware

Crea un archivo middleware.ts en la raiz de tu proyecto (junto a app/):

tsx
// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  // Se ejecuta antes de cada request que haga match
  console.log("Request a:", request.nextUrl.pathname)

  return NextResponse.next()
}

// Configurar en que rutas se ejecuta
export const config = {
  matcher: [
    // Se ejecuta en todo excepto archivos estaticos
    "/((?!_next/static|_next/image|favicon.ico).*)",
  ],
}

Autenticacion

El caso mas comun: proteger rutas que requieren login:

tsx
// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token")?.value
  const isAuthPage = request.nextUrl.pathname.startsWith("/login")
  const isProtectedRoute = request.nextUrl.pathname.startsWith("/dashboard")

  // Si intenta acceder a ruta protegida sin token, redirigir a login
  if (isProtectedRoute && !token) {
    const loginUrl = new URL("/login", request.url)
    loginUrl.searchParams.set("from", request.nextUrl.pathname)
    return NextResponse.redirect(loginUrl)
  }

  // Si ya tiene token y va a login, redirigir a dashboard
  if (isAuthPage && token) {
    return NextResponse.redirect(new URL("/dashboard", request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ["/dashboard/:path*", "/login"],
}

Con verificacion JWT

tsx
// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
import { jwtVerify } from "jose"

const secret = new TextEncoder().encode(process.env.JWT_SECRET)

export async function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token")?.value

  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url))
  }

  try {
    const { payload } = await jwtVerify(token, secret)

    // Agregar info del usuario como header para la pagina
    const response = NextResponse.next()
    response.headers.set("x-user-id", payload.sub as string)
    response.headers.set("x-user-role", payload.role as string)
    return response
  } catch {
    // Token invalido o expirado
    return NextResponse.redirect(new URL("/login", request.url))
  }
}

export const config = {
  matcher: ["/dashboard/:path*", "/admin/:path*"],
}

Redirects

tsx
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // Redirect permanente (301)
  if (pathname === "/blog-viejo") {
    return NextResponse.redirect(new URL("/blog", request.url), 301)
  }

  // Redirect temporal (307)
  if (pathname === "/promo") {
    return NextResponse.redirect(new URL("/ofertas", request.url))
  }

  return NextResponse.next()
}

Headers personalizados

tsx
export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  // Headers de seguridad
  response.headers.set("X-Frame-Options", "DENY")
  response.headers.set("X-Content-Type-Options", "nosniff")
  response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin")

  return response
}

Rate limiting basico

tsx
const rateLimitMap = new Map<string, { count: number; lastReset: number }>()

export function middleware(request: NextRequest) {
  if (!request.nextUrl.pathname.startsWith("/api")) {
    return NextResponse.next()
  }

  const ip = request.headers.get("x-forwarded-for") || "unknown"
  const now = Date.now()
  const windowMs = 60 * 1000 // 1 minuto
  const maxRequests = 60

  const entry = rateLimitMap.get(ip)

  if (!entry || now - entry.lastReset > windowMs) {
    rateLimitMap.set(ip, { count: 1, lastReset: now })
    return NextResponse.next()
  }

  if (entry.count >= maxRequests) {
    return NextResponse.json(
      { error: "Demasiadas peticiones. Intenta en un minuto." },
      { status: 429 }
    )
  }

  entry.count++
  return NextResponse.next()
}
Limitacion del middleware

El middleware corre en el Edge Runtime. No puedes usar paquetes de Node.js que dependan de APIs nativas (como fs, crypto completo, etc.). Usa librerias compatibles con Edge como jose para JWT.

Matcher patterns

tsx
export const config = {
  matcher: [
    // Una ruta especifica
    "/dashboard",

    // Una ruta y todas sus hijas
    "/dashboard/:path*",

    // Multiples rutas
    "/dashboard/:path*",
    "/admin/:path*",
    "/api/:path*",

    // Todo excepto archivos estaticos
    "/((?!_next/static|_next/image|favicon.ico).*)",
  ],
}

Ejemplo: proteger un dashboard completo

tsx
// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  const token = request.cookies.get("session")?.value

  if (!token) {
    const url = new URL("/login", request.url)
    url.searchParams.set("redirect", request.nextUrl.pathname)
    return NextResponse.redirect(url)
  }

  // Verificar rol para admin
  if (request.nextUrl.pathname.startsWith("/admin")) {
    // En produccion, verificarias el JWT aqui
    const isAdmin = request.cookies.get("role")?.value === "admin"
    if (!isAdmin) {
      return NextResponse.redirect(new URL("/dashboard", request.url))
    }
  }

  return NextResponse.next()
}

export const config = {
  matcher: ["/dashboard/:path*", "/admin/:path*"],
}