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.
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
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 9Five 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 9Fastify hooks (onRequest) and the SecureNow preload both work cleanly. Here's the production setup for IP blocking and user-agent filtering.
May 9