@universal-rate-limit/nextjs
v2.2.0
Published
Next.js middleware and API route wrapper for universal-rate-limit
Maintainers
Readme
Next.js App Router wrapper and proxy/middleware limiter for universal-rate-limit — a zero-dependency rate limiter built on web standards. Supports fixed-window, sliding-window, and token-bucket algorithms, pluggable stores (memory, Redis, or your own), and IETF-compliant rate limit headers out of the box.
Try the playground to see rate limiting in action.
Install
npm install @universal-rate-limit/nextjsUsage
App Router Route Handlers
Wrap your route handler with withRateLimit:
// app/api/hello/route.ts
import { withRateLimit } from '@universal-rate-limit/nextjs';
async function handler(request: Request) {
return Response.json({ hello: 'world' });
}
export const GET = withRateLimit(handler, {
algorithm: { type: 'sliding-window', windowMs: 60_000 }, // 1 minute
limit: 60 // 60 requests per window
});withRateLimit forwards the route context, so dynamic routes receive their params:
// app/api/users/[id]/route.ts
import { withRateLimit } from '@universal-rate-limit/nextjs';
async function handler(request: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return Response.json({ id });
}
export const GET = withRateLimit(handler, { limit: 30 });Proxy (site-wide)
Use nextjsRateLimit in proxy.ts (Next.js 16). On Next.js 14/15 the same code goes in middleware.ts with the function named middleware.
// proxy.ts
import { nextjsRateLimit } from '@universal-rate-limit/nextjs';
import { NextResponse } from 'next/server';
const limiter = nextjsRateLimit({
algorithm: { type: 'sliding-window', windowMs: 60_000 },
limit: 60
});
export async function proxy(request: Request) {
const result = await limiter(request);
if (result.limited) {
return new Response('Too Many Requests', {
status: 429,
headers: result.headers
});
}
const response = NextResponse.next();
for (const [key, value] of Object.entries(result.headers)) {
response.headers.set(key, value);
}
return response;
}
export const config = { matcher: ['/api/:path*'] };Production: use a shared store
The default MemoryStore lives in one process. Serverless functions (Vercel and similar), proxy instances and multiple servers each keep their own counts, so the effective limit is multiplied by the number of instances. Pass a
RedisStore to share state. Clients such as ioredis and node-redis need the Node.js runtime: Next.js 16 runs proxy.ts on Node.js, while Next.js 14/15 middleware.ts defaults to the Edge runtime (opt into Node.js
with runtime: 'nodejs' in its config on 15.5+).
The default key reads x-forwarded-for and related headers. That is safe on Vercel, which overwrites them; behind another proxy or CDN, set keyGenerator to a header your proxy controls.
Options
Both withRateLimit and nextjsRateLimit accept all core options — limit, algorithm, cost, store, keyGenerator, skip, handler, message, statusCode, headers, legacyHeaders, failOpen, and prefix.
Example
See examples/nextjs for a complete Next.js App Router app with integration tests.
Documentation
License
MIT
