Guides
Next.js Integration
Integrate rate limiting into your Next.js API routes. Works with both App Router and Pages Router.
Next.js Integration
App Router
// app/api/route.ts
import { createClient } from 'limitly-sdk';
import { NextResponse } from 'next/server';
const client = createClient({ serviceId: 'nextjs-api' });
export async function GET(request: Request) {
const userId = request.headers.get('x-user-id') || 'anonymous';
const result = await client.checkRateLimit(userId);
if (!result.allowed) {
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 });
}
return NextResponse.json({ success: true });
}Reusable Helper
// lib/rate-limit.ts
import { createClient } from 'limitly-sdk';
// Recommended: Use your own Redis
const client = createClient({
redisUrl: process.env.REDIS_URL,
serviceId: 'nextjs-api'
});
export async function withRateLimit(request: Request, handler: Function) {
const userId = request.headers.get('x-user-id') || 'anonymous';
const result = await client.checkRateLimit(userId);
if (!result.allowed) {
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 });
}
return handler(request);
}Usage:
export async function GET(request: Request) {
return withRateLimit(request, async () => {
return NextResponse.json({ data: 'Protected data' });
});
}Per-Route Limits
// lib/rate-limit.ts
import { createClient } from 'limitly-sdk';
import { NextResponse } from 'next/server';
const client = createClient({ serviceId: 'nextjs-api' });
export async function checkRouteLimit(
request: Request,
route: string,
capacity: number,
refillRate: number
): Promise<NextResponse | null> {
const userId = request.headers.get('x-user-id') ||
request.headers.get('x-forwarded-for')?.split(',')[0] ||
'anonymous';
const result = await client.checkRateLimit({
identifier: `${userId}:${route}`,
capacity,
refillRate
});
const headers = new Headers();
if (result.limit) headers.set('X-RateLimit-Limit', result.limit.toString());
if (result.remaining !== undefined) {
headers.set('X-RateLimit-Remaining', result.remaining.toString());
}
if (!result.allowed) {
const retryAfter = result.reset
? Math.ceil((result.reset - Date.now()) / 1000)
: 60;
headers.set('Retry-After', retryAfter.toString());
return NextResponse.json(
{ error: 'Rate limit exceeded', retryAfter },
{ status: 429, headers }
);
}
return null; // Rate limit passed
}Use in routes:
// app/api/login/route.ts
import { checkRouteLimit } from '@/lib/rate-limit';
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
// Strict limits for login
const rateLimitResponse = await checkRouteLimit(request, '/api/login', 5, 0.1);
if (rateLimitResponse) return rateLimitResponse;
// Login logic
return NextResponse.json({ success: true });
}Pages Router
// pages/api/route.ts
import { createClient } from 'limitly-sdk';
const client = createClient({ serviceId: 'nextjs-api' });
export default async function handler(req, res) {
const userId = req.headers['x-user-id'] || 'anonymous';
const result = await client.checkRateLimit(userId);
if (!result.allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
res.status(200).json({ success: true });
}Server Actions
'use server';
import { createClient } from 'limitly-sdk';
import { headers } from 'next/headers';
const client = createClient({ serviceId: 'server-actions' });
export async function protectedAction(data: FormData) {
const userId = headers().get('x-user-id') || 'anonymous';
const result = await client.checkRateLimit(userId);
if (!result.allowed) throw new Error('Rate limit exceeded');
return { success: true };
}Best Practices
- Use environment variables for configuration
- Handle errors gracefully
- Use authenticated user IDs when available