ratelimit-next
v0.1.1
Published
Rate limiting for Next.js. Zero dependencies.
Maintainers
Readme
ratelimit-next
Rate limiting for Next.js. Zero dependencies.
Declarative rules, type-safe API, pluggable stores. Works in Edge Runtime, Node.js, and serverless.
Install
npm install ratelimit-nextQuick Start
1. Define rules
// lib/rate-limit.ts
import { createFloodgate } from "ratelimit-next";
export const gate = createFloodgate({
rules: {
api: { limit: 100, window: "1m" },
auth: { limit: 5, window: "15m", algorithm: "fixed-window" },
uploads: { limit: 10, window: "1h", algorithm: "token-bucket" },
},
});2. Protect routes
// app/api/data/route.ts
import { withRateLimit } from "ratelimit-next/next";
import { gate } from "@/lib/rate-limit";
export const GET = withRateLimit(gate, "api", async (request) => {
return Response.json({ data: "hello" });
});3. Done
Clients get standard rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) on every response and a 429 with Retry-After when limited.
Algorithms
Fixed Window
Divides time into fixed intervals and counts requests per interval. Simple and predictable, but allows bursts at window boundaries.
{ limit: 100, window: "1m", algorithm: "fixed-window" }Sliding Window (default)
Approximates a true sliding window by weighting the previous interval's count. Smoother than fixed window with minimal overhead.
{ limit: 100, window: "1m", algorithm: "sliding-window" }Token Bucket
Tokens refill at a steady rate. Allows short bursts while maintaining an average rate. Best for APIs where occasional spikes are acceptable.
{ limit: 100, window: "1m", algorithm: "token-bucket" }Window Format
Duration strings: "10s" (seconds), "5m" (minutes), "1h" (hours), "1d" (days).
Next.js Middleware
Apply rate limiting globally via Next.js middleware:
// middleware.ts
import { createFloodgate } from "ratelimit-next";
import { createRateLimitMiddleware } from "ratelimit-next/next";
const gate = createFloodgate({
rules: { api: { limit: 100, window: "1m" } },
});
export default createRateLimitMiddleware(gate, {
rule: "api",
paths: ["/api/"],
});
export const config = { matcher: "/api/:path*" };API Route Protection
Three styles to choose from:
withRateLimit (HOF)
import { withRateLimit } from "ratelimit-next/next";
export const GET = withRateLimit(gate, "api", async (request) => {
return Response.json({ ok: true });
});rateLimit (guard)
import { rateLimit } from "ratelimit-next/next";
export async function GET(request: Request) {
const limited = await rateLimit(gate, "api", request);
if (limited) return limited;
return Response.json({ ok: true });
}gate.limit (throws)
import { gate } from "@/lib/rate-limit";
import { RateLimitError } from "ratelimit-next";
export async function GET(request: Request) {
try {
await gate.limit("api", ip(request));
return Response.json({ ok: true });
} catch (e) {
if (e instanceof RateLimitError) {
return Response.json({ error: "Too many requests" }, { status: 429 });
}
throw e;
}
}React
Client-side components for rate-limit-aware UIs:
import { RateLimited, useRateLimit, RateLimitProvider } from "ratelimit-next/react";
// Wrap your app (optional — configures the check endpoint)
<RateLimitProvider endpoint="/api/rate-limit">
<App />
</RateLimitProvider>
// Declarative
<RateLimited rule="api-calls" fallback={<p>Too many requests. Please wait.</p>}>
<MyForm />
</RateLimited>
// Hook
function SubmitButton() {
const { allowed, remaining, check } = useRateLimit("api-calls");
return (
<button disabled={!allowed} onClick={() => { /* submit */ await check(); }}>
Submit ({remaining} left)
</button>
);
}The React components call GET /api/rate-limit?rule=<name> to check status. Implement this endpoint with your floodgate instance:
// app/api/rate-limit/route.ts
export async function GET(request: Request) {
const url = new URL(request.url);
const rule = url.searchParams.get("rule") ?? "api";
const result = await gate.check(rule);
return Response.json(result);
}Stores
Memory (default)
Zero-config, works everywhere. State is lost on restart. Good for development and single-instance deployments.
import { createFloodgate, MemoryStore } from "ratelimit-next";
const gate = createFloodgate({
rules: { api: { limit: 100, window: "1m" } },
store: new MemoryStore(), // this is the default
});Redis
For multi-instance deployments. Requires ioredis.
import Redis from "ioredis";
import { createFloodgate } from "ratelimit-next";
import { createRedisStore } from "ratelimit-next/adapters/redis";
const gate = createFloodgate({
rules: { api: { limit: 100, window: "1m" } },
store: createRedisStore(new Redis(process.env.REDIS_URL)),
});Vercel KV
For Vercel deployments. Requires @vercel/kv.
import { kv } from "@vercel/kv";
import { createFloodgate } from "ratelimit-next";
import { createVercelKVStore } from "ratelimit-next/adapters/vercel-kv";
const gate = createFloodgate({
rules: { api: { limit: 100, window: "1m" } },
store: createVercelKVStore(kv),
});Custom Key Resolvers
By default, rate limits are keyed by IP address. Customize this:
const gate = createFloodgate({
rules: { api: { limit: 100, window: "1m" } },
keyResolver: (request) => {
// Key by API token
return request.headers.get("Authorization") ?? "anonymous";
},
});Or pass keys directly:
const result = await gate.check("api", userId);API Reference
createFloodgate(config)
Returns a Floodgate instance with:
| Method | Description |
|---|---|
| check(rule, key?) | Check limit, returns RateLimitResult |
| limit(rule, key?) | Check limit, throws RateLimitError if exceeded |
| reset(rule, key) | Clear limit for a key |
| headers(result) | Generate HTTP headers from a result |
RateLimitResult
{
allowed: boolean;
limit: number;
remaining: number;
reset: number; // timestamp ms
retryAfter?: number; // seconds (present when blocked)
}License
MIT
