seguridad·6 min de lectura

Rate Limiting en Next.js: Protege tus APIs

Implementa rate limiting en tus API routes de Next.js. Limita requests por IP, protege endpoints sensibles y evita abuso con patrones simples.

Rate Limiting en Next.js: Protege tus APIs

Rate limiting es la primera línea de defensa de tus API routes. Sin el, cualquiera puede hacer miles de requests por segundo a tu endpoint de login, agotar tu cuota de base de datos, o disparar tu factura de Vercel. Es trivial de implementar y crítico para producción.

Si tu app tiene endpoints publicos (login, registro, contacto, webhooks), necesitas rate limiting. Punto.

Rate limiter simple (en memoria)

Para proyectos chicos o entornos de una sola instancia, un Map en memoria es suficiente:

typescript
// lib/rate-limit.ts
const requests = new Map<string, { count: number; resetTime: number }>();
 
export function rateLimit(
  ip: string,
  limit: number = 10,
  windowMs: number = 60_000
): { allowed: boolean; remaining: number } {
  const now = Date.now();
  const record = requests.get(ip);
 
  // Primera request o ventana expirada
  if (!record || now > record.resetTime) {
    requests.set(ip, { count: 1, resetTime: now + windowMs });
    return { allowed: true, remaining: limit - 1 };
  }
 
  // Dentro de la ventana
  if (record.count < limit) {
    record.count++;
    return { allowed: true, remaining: limit - record.count };
  }
 
  // límite alcanzado
  return { allowed: false, remaining: 0 };
}

Usarlo en una API route

typescript
// app/api/contacto/route.ts
import { NextResponse } from "next/server";
import { rateLimit } from "@/lib/rate-limit";
 
export async function POST(request: Request) {
  const ip = request.headers.get("x-forwarded-for") ?? "unknown";
 
  const { allowed, remaining } = rateLimit(ip, 5, 60_000); // 5 por minuto
 
  if (!allowed) {
    return NextResponse.json(
      { error: "Demasiadas solicitudes. Intenta en un minuto." },
      {
        status: 429,
        headers: { "Retry-After": "60" },
      }
    );
  }
 
  // Procesar la request normalmente
  const body = await request.json();
  // ... tu lógica aquí
 
  return NextResponse.json(
    { ok: true },
    { headers: { "X-RateLimit-Remaining": String(remaining) } }
  );
}

Rate limiting con Upstash (serverless)

En serverless (Vercel, Cloudflare), cada invocación puede correr en una instancia diferente. Un Map en memoria no sirve porque cada instancia tiene su propio Map. Necesitas un store compartido.

Upstash tiene Redis serverless con un plan gratuito de 10K requests/día:

bash
npm install @upstash/ratelimit @upstash/redis
typescript
// lib/rate-limit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
 
export const rateLimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "60 s"), // 10 requests por minuto
  analytics: true,
});
typescript
// app/api/login/route.ts
import { NextResponse } from "next/server";
import { rateLimit } from "@/lib/rate-limit";
 
export async function POST(request: Request) {
  const ip = request.headers.get("x-forwarded-for") ?? "unknown";
  const { success, remaining } = await rateLimit.limit(ip);
 
  if (!success) {
    return NextResponse.json(
      { error: "Demasiados intentos" },
      { status: 429, headers: { "Retry-After": "60" } }
    );
  }
 
  // ... tu lógica de login
}

Dos líneas de config, una línea para verificar. Upstash se encarga del almacenamiento distribuido.

Rate limiting en middleware (global)

Si quieres proteger TODAS tus API routes:

typescript
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
 
const requests = new Map<string, { count: number; resetTime: number }>();
 
export function middleware(request: NextRequest) {
  // Solo aplicar a API routes
  if (!request.nextUrl.pathname.startsWith("/api")) {
    return NextResponse.next();
  }
 
  const ip = request.headers.get("x-forwarded-for") ?? "unknown";
  const now = Date.now();
  const record = requests.get(ip);
 
  if (!record || now > record.resetTime) {
    requests.set(ip, { count: 1, resetTime: now + 60_000 });
    return NextResponse.next();
  }
 
  if (record.count >= 30) { // 30 requests por minuto global
    return NextResponse.json(
      { error: "Rate limit exceeded" },
      { status: 429 }
    );
  }
 
  record.count++;
  return NextResponse.next();
}

límites recomendados

EndpointlímiteRazon
Login5/min por IPPrevenir brute force
Registro3/min por IPPrevenir spam de cuentas
Contacto2/min por IPPrevenir spam
API general30/min por IPUso normal
Webhooks100/min por IPLos servicios envian rafagas

Siguiente paso

Rate limiting es una capa de seguridad. Para protección más completa, revisa la guía de seguridad en aplicaciones Next.js y la guía de headers de seguridad para CSP, HSTS y demas.

#nextjs#seguridad#rate-limiting#api#middleware

Preguntas frecuentes

¿Qué es rate limiting?

Es una técnica que limita cuántas requests puede hacer un usuario o IP en un período de tiempo. Si alguien hace 100 requests por segundo a tu API, el rate limiter bloquea las requests que excedan el límite y responde con un 429 Too Many Requests.

¿Necesito rate limiting si uso Vercel?

Si. Vercel tiene protecciones básicas contra DDoS, pero no limita el uso de tus API routes. Un bot puede hacer miles de requests a tu endpoint de login, y tu pagas por cada invocación de serverless function.

¿Donde guardo el contador de requests?

Para una sola instancia, un Map en memoria funciona. Para múltiples instancias o serverless, necesitas un store externo como Redis (Upstash tiene un plan gratuito) o una base de datos.

¿Qué código HTTP devuelvo cuando limito?

429 Too Many Requests. Incluye un header Retry-After con los segundos que el cliente debe esperar antes de reintentar.