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*"],
}