Rate Limiting in Nuxt That Actually Works

Nuxt 3 doesn't ship a rate-limit module, but Nitro's event handlers + Upstash Redis give you production-grade rate limiting in 30 lines.

May 9, 2026·4 min read

Rate Limiting in Nuxt That Actually Works

Nuxt 3 doesn't ship rate limiting; you build it on top of Upstash Redis or another HTTP-accessible store. Here's the 30-line version.

Setup

npm install @upstash/ratelimit @upstash/redis

Set environment variables: UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN.

The middleware

// server/middleware/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const redis = Redis.fromEnv();

const generalLimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(100, '1 m'),
  prefix: '@app/general',
});

const authLimit = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(5, '5 m'),
  prefix: '@app/auth',
});

export default defineEventHandler(async (event) => {
  const path = getRequestURL(event).pathname;
  if (!path.startsWith('/api/')) return;

  const ip = getRequestHeader(event, 'x-forwarded-for')?.split(',')[0]?.trim()
    || getRequestIP(event)
    || 'unknown';

  const limit = path.startsWith('/api/auth') ? authLimit : generalLimit;
  const { success, limit: max, remaining, reset } = await limit.limit(ip);

  setResponseHeader(event, 'X-RateLimit-Limit', String(max));
  setResponseHeader(event, 'X-RateLimit-Remaining', String(remaining));
  setResponseHeader(event, 'X-RateLimit-Reset', String(reset));

  if (!success) {
    setResponseStatus(event, 429);
    return { error: 'Rate limit exceeded' };
  }
});

Per-user keying

For authenticated routes, prefer user ID over IP:

const session = await getUserSession(event); // your auth helper
const key = session?.userId || ip;
const result = await limit.limit(key);

Costs

Upstash free tier: 10K commands/day. Each rate-limit check is ~3 commands. Free tier covers ~3.3K rate-limited requests/day. Upgrade to paid ($0.20 per 100K commands) when you outgrow it.

What this doesn't solve

Same as any rate limiter: distributed attacks, bot traffic that mimics legitimate users, application-level abuse. Layer with the SecureNow firewall for IP-reputation blocking.

Related

Frequently Asked Questions

Is there a Nuxt module for rate limiting?

Not a first-party one. The community has nuxt-rate-limit but it's not actively maintained as of 2026. Hand-rolling on top of Upstash Redis is the more reliable path.

Does this work on Vercel and self-host?

Yes — Upstash Redis is HTTP-accessible from both. The same code runs in either deployment.

How do I scope to specific routes?

Filter on `getRequestURL(event).pathname` inside the middleware. Apply different limits per pattern.

Recommended reading

Create Custom Alert Rules From the Command Line

SecureNow 8.1 adds `securenow alerts rules create` — define your own detection rules from SQL, scope them to your apps, and ship them without leaving the terminal. Here's how, with a real magic-link brute-force example.

Jun 11
Secure a Next.js App with SecureNow Using This AI Onboarding Prompt

A copy-paste prompt that lets an AI coding agent install SecureNow, wire Next.js instrumentation, verify traces and logs, deploy to AWS, simulate attacks, and prove firewall blocking with human approval gates.

May 18
nextjs securenow ai onboarding prompt
Adding Backend Tracing to a Sentry Stack with OpenTelemetry

If your team uses Sentry for frontend errors and needs backend distributed tracing without doubling the Sentry bill, here's the OpenTelemetry path that doesn't make you choose.

May 9