Next.js cookbook · Intermediate

Rate limit Route Handlers and Server Actions

Protect login, signup and AI endpoints from abuse in a serverless deployment.

Approach

Serverless functions don't share memory, so use a shared store. @upstash/ratelimit with Upstash Redis (HTTP-based, works at the edge) provides sliding-window and token-bucket limiters. Key by user ID or IP, and return 429 with Retry-After.

Solution

// lib/ratelimit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

export const loginLimiter = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, "15 m"),
  prefix: "rl:login",
  analytics: true,
});

// app/api/auth/login/route.ts
import { headers } from "next/headers";
import { loginLimiter } from "@/lib/ratelimit";

export async function POST(request: Request) {
  const ip = (await headers()).get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown";
  const { success, reset, remaining } = await loginLimiter.limit(ip);
  if (!success) {
    return Response.json({ title: "Too many attempts" }, {
      status: 429,
      headers: { "Retry-After": String(Math.ceil((reset - Date.now()) / 1000)), "RateLimit-Remaining": String(remaining) },
    });
  }
  // ... verify credentials
}

Pitfalls

  • In-memory counters in serverless (reset per instance).
  • Trusting x-forwarded-for when not behind a trusted proxy (on Vercel it's set by the platform).
  • Rate limiting only the UI path while the action/API remains unprotected.

Variations

  • Platform firewalls (Vercel WAF, Cloudflare) for broad bot/rate protection.
  • Self-hosted Redis with a Lua-based limiter on Node runtimes.

Further reading: Upstash Ratelimit

Reference: https://upstash.com/docs/redis/sdks/ratelimit-ts/overview

More Next.js recipes