Rate Limiting
rateLimitMiddleware with Upstash in production, plus credits as the spend limit.
What ships
lib/rateLimit.js exports rateLimitMiddleware. With UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN set it uses Upstash Redis, so limits hold across serverless instances. Without them it falls back to an in-memory map, which is fine locally and useless in production, where every instance keeps its own count. npm run deploy warns if the app URL is real and Upstash is unset.
Use it in a route
Key on the user ID for signed-in routes so one user cannot exhaust a shared IP, and keep limits per route.
import { rateLimitMiddleware } from "@/lib/rateLimit";
const RATE_LIMIT = { limit: 20, windowMs: 60 * 1000 };
export async function POST(req) {
const auth = await getAuthenticatedUser(req);
if (auth.error) return NextResponse.json({ error: auth.error }, { status: auth.status });
const limited = await rateLimitMiddleware(req, { ...RATE_LIMIT, userId: auth.user.id });
if (limited) return limited; // a ready 429 response
// validate → consume_credit → call the model → persist → return
}Credits are the second limit
Rate limiting stops bursts. Credits stop runaway spend: consume_credit is an atomic database function, so two parallel requests cannot both succeed on the last credit.