TechLead

Middleware y Rutas API

Implementa middleware para autenticación, redirecciones y construye endpoints API

Middleware en Next.js

El middleware se ejecuta antes de que se complete una petición. Te permite modificar la respuesta, redirigir, reescribir URLs o establecer headers basados en la petición entrante. El middleware se ejecuta en Edge Runtime para ejecución rápida.

🔧 Casos de Uso de Middleware

  • ✅ Autenticación y autorización
  • ✅ Redirecciones basadas en ubicación/dispositivo
  • ✅ Pruebas A/B y feature flags
  • ✅ Detección de bots y limitación de rate
  • ✅ Logging y analytics

Middleware Básico

El middleware se ejecuta antes de que se complete una petición, lo que lo hace ideal para inspección ligera de peticiones, logging o redirecciones.

// middleware.ts (raíz de tu proyecto)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Obtener el pathname
  const { pathname } = request.nextUrl;
  
  // Registrar cada petición
  console.log('Petición a:', pathname);
  
  // Continuar al siguiente middleware o ruta
  return NextResponse.next();
}

// Configurar qué rutas ejecutan middleware
export const config = {
  matcher: [
    // Coincidir todas las rutas excepto archivos estáticos
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

Middleware de Autenticación

Protege rutas sensibles verificando cookies y redirigiendo usuarios no autenticados antes de que se renderice una página.

// 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 { pathname } = request.nextUrl;
  
  // Rutas protegidas
  if (pathname.startsWith('/dashboard')) {
    if (!token) {
      // Redirigir a login con URL de retorno
      const loginUrl = new URL('/login', request.url);
      loginUrl.searchParams.set('from', pathname);
      return NextResponse.redirect(loginUrl);
    }
  }
  
  // Redirigir usuarios con sesión lejos de páginas de auth
  if (pathname.startsWith('/login') || pathname.startsWith('/signup')) {
    if (token) {
      return NextResponse.redirect(new URL('/dashboard', request.url));
    }
  }
  
  return NextResponse.next();
}

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

Redirecciones y Reescrituras

Las redirecciones cambian la URL en el navegador, mientras que las reescrituras sirven contenido diferente sin cambiar la URL visible.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname, searchParams } = request.nextUrl;
  
  // Redirigir URLs antiguas
  if (pathname === '/old-page') {
    return NextResponse.redirect(new URL('/new-page', request.url));
  }
  
  // Reescritura (URL permanece igual, se sirve página diferente)
  if (pathname === '/blog') {
    return NextResponse.rewrite(new URL('/news', request.url));
  }
  
  // Redirección basada en geo
  const country = request.geo?.country || 'US';
  if (country === 'GB' && pathname === '/') {
    return NextResponse.rewrite(new URL('/uk', request.url));
  }
  
  // Pruebas A/B
  const bucket = request.cookies.get('ab-test')?.value || 
    (Math.random() > 0.5 ? 'a' : 'b');
  
  if (pathname === '/pricing') {
    const response = NextResponse.rewrite(
      new URL(`/pricing-${bucket}`, request.url)
    );
    response.cookies.set('ab-test', bucket);
    return response;
  }
  
  return NextResponse.next();
}

Establecer Headers

Puedes leer o modificar headers para pasar contexto de petición hacia abajo o establecer políticas de respuesta como cacheo y CORS.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Clonar los headers de la petición
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-custom-header', 'my-value');
  
  // Crear respuesta con headers modificados
  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });
  
  // Establecer headers de respuesta
  response.headers.set('x-middleware-cache', 'no-cache');
  
  // Headers CORS
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST');
  
  return response;
}

Route Handlers (Rutas API)

Los Route Handlers viven en el App Router y te permiten construir endpoints RESTful junto a tu UI.

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

// GET /api/users
export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const page = searchParams.get('page') || '1';
  
  const users = await db.user.findMany({
    skip: (parseInt(page) - 1) * 10,
    take: 10,
  });
  
  return NextResponse.json(users);
}

// POST /api/users
export async function POST(request: NextRequest) {
  const body = await request.json();
  
  const user = await db.user.create({
    data: body,
  });
  
  return NextResponse.json(user, { status: 201 });
}

// app/api/users/[id]/route.ts
// GET /api/users/:id
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const user = await db.user.findUnique({ where: { id } });
  
  if (!user) {
    return NextResponse.json(
      { error: 'Usuario no encontrado' },
      { status: 404 }
    );
  }
  
  return NextResponse.json(user);
}

// DELETE /api/users/:id
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  await db.user.delete({ where: { id } });
  return new NextResponse(null, { status: 204 });
}

Características de Route Handler

Los Route Handlers pueden leer cookies y headers, transmitir respuestas y configurar comportamiento de cacheo.

import { NextRequest, NextResponse } from 'next/server';
import { cookies, headers } from 'next/headers';

export async function GET(request: NextRequest) {
  // Leer cookies
  const cookieStore = await cookies();
  const token = cookieStore.get('token');
  
  // Leer headers
  const headersList = await headers();
  const auth = headersList.get('authorization');
  
  // Establecer cookies en respuesta
  const response = NextResponse.json({ success: true });
  response.cookies.set('session', 'abc123', {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 60 * 60 * 24 * 7, // 1 semana
  });
  
  return response;
}

// Respuesta streaming
export async function GET() {
  const stream = new ReadableStream({
    async start(controller) {
      for (let i = 0; i < 10; i++) {
        controller.enqueue(`Fragmento de datos ${i}\n`);
        await new Promise(r => setTimeout(r, 100));
      }
      controller.close();
    },
  });
  
  return new Response(stream);
}

// Cacheo
export const revalidate = 60; // Cachear por 60 segundos
export const dynamic = 'force-static'; // Siempre cachear

📖 Documentación de Middleware →

✅ Mejores Prácticas de Middleware y API

  • • Mantén el middleware ligero - se ejecuta en cada petición coincidente
  • • Usa configuración matcher para limitar qué rutas ejecutan middleware
  • • Prefiere Server Actions sobre rutas API para mutaciones
  • • Usa Route Handlers para webhooks e integraciones API externas
  • • Agrega manejo de errores apropiado y códigos de estado
  • • Considera limitaciones de Edge Runtime (sin APIs de Node.js)