# Rate Limiting
Sliding window rate limiting for Next.js 14 API routes — in-memory for development, drop-in Upstash Redis upgrade for production, with per-IP and per-user strategies, standard headers, and pre-tuned configs for auth, purchases, AI, and webhooks.
## What's included
**Configuration**
- `RATE_LIMIT_CONFIG` — per-endpoint limits for `api`, `auth`, `auth_register`, `purchase`, `ai_customize`, `webhook`, `payout`, `search`; edit values here to change limits globally
- `RateLimitKey` — union type of all config keys
**In-memory store (dev / single instance)**
- `checkRateLimit(identifier, type)` — increments hit count and returns a `RateLimitResult`; resets after the window expires; auto-cleans expired entries every 5 minutes
- `peekRateLimit(identifier, type)` — reads current state without incrementing; use for dashboard displays
- `resetRateLimit(identifier, type)` — clears a key; use after successful auth to reset brute-force counters
**Response helpers**
- `rateLimitHeaders(result)` — returns `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` as a plain object
- `tooManyRequests(result)` — returns a `NextResponse` with status 429, JSON body, and rate limit headers
- `getIP(req)` — extracts real IP from `cf-connecting-ip` → `x-forwarded-for` → `x-real-ip` → `'anonymous'`
**Route wrappers**
- `withRateLimit(type, handler, options?)` — wraps a route handler; rate-limits by IP by default; accepts `getIdentifier` to use any custom key; appends headers to successful responses
- `withAuthRateLimit(type, handler)` — same as above but extracts user ID from the NextAuth JWT cookie; falls back to IP if unauthenticated
**Production (Upstash Redis)**
- `checkRateLimitRedis(identifier, type)` — same interface as `checkRateLimit` but backed by Upstash sliding window; `RedisLimiters` map is exported for direct access; the entire Redis block is commented out — uncomment when ready
## Setup
### 1. Install dependencies
```bash
# Development — no extra deps needed
# Production only
npm install @upstash/ratelimit @upstash/redis
```
### 2. Environment variables
```
# Production only (Upstash)
UPSTASH_REDIS_REST_URL=your Upstash Redis REST URL
UPSTASH_REDIS_REST_TOKEN=your Upstash Redis REST token
# Required only if using withAuthRateLimit
NEXTAUTH_SECRET=your NextAuth secret
```
### 3. Add to your project
```ts
import { withRateLimit } from '@/blocks/ratelimit'
```
No other configuration needed for dev. For production, uncomment the Redis block at the bottom of the file and swap `checkRateLimit` calls for `checkRateLimitRedis`.
## Usage examples
```ts
// Per-IP rate limiting — wrap the handler directly
import { withRateLimit } from '@/blocks/ratelimit'
import { NextResponse } from 'next/server'
export const POST = withRateLimit('auth', async (req) => {
// handle sign-in attempt
return NextResponse.json({ ok: true })
})
```
```ts
// Per-user rate limiting on an authenticated route
import { withAuthRateLimit } from '@/blocks/ratelimit'
import { NextResponse } from 'next/server'
export const POST = withAuthRateLimit('ai_customize', async (req) => {
const result = await runAI(req)
return NextResponse.json({ result })
})
```
```ts
// Manual check — when you need the result inline before doing work
import { checkRateLimit, tooManyRequests, resetRateLimit } from '@/blocks/ratelimit'
export async function POST(req) {
const result = checkRateLimit(session.user.id, 'purchase')
if (!result.success) return tooManyRequests(result)
const order = await createOrder(session.user.id)
// Reset after a successful purchase so the user isn't penalised
// for legitimate retries on a different item
resetRateLimit(session.user.id, 'purchase')
return Response.json(order)
}
```
## Notes
- The in-memory store does not survive Lambda cold starts or scale across multiple Vercel instances — on Vercel, each serverless function instance has its own store; this is fine for development and low-traffic deployments, but switch to Upstash before going to production at any meaningful scale
- `withAuthRateLimit` dynamically imports `next-auth/jwt` — if you're not using NextAuth, replace the `getIdentifier` function with your own session extraction logic
- `RATE_LIMIT_CONFIG` is `as const` — to override a limit for a specific route without changing the global config, pass a custom `getIdentifier` that appends a suffix to the key (e.g. `userId + ':strict'`) and add that key to the config
- The `webhook` config is set to 500 req/min intentionally — Razorpay/PayPal can fire bursts of retries; tightening this will cause missed payment events