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.

Lhoussine
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

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
How to Block Bot Traffic in Express With No Extra Infra

Five approaches to bot blocking in Express, ranked by effort vs. effectiveness. From a 5-line allowlist to a full IP-reputation firewall — all without Cloudflare, AWS WAF, or any new infrastructure.

May 9
How to Block Bot Traffic in Fastify With No Extra Infra

Fastify hooks (onRequest) and the SecureNow preload both work cleanly. Here's the production setup for IP blocking and user-agent filtering.

May 9