.claude/skills/rate-limiting/SKILL.md
Rate limiting patterns for all API endpoints in this Next.js application. Covers distributed rate limiting with Redis, tier selection, HOC patterns, and security best practices. Use this skill when creating new API endpoints or reviewing rate limiting implementation.
npx skillsauth add NextSpark-js/nextspark rate-limitingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Rate limiting patterns and best practices for protecting API endpoints from abuse and DDoS attacks.
core/lib/
├── api/
│ └── rate-limit.ts # withRateLimitTier HOC, checkDistributedRateLimit
└── rate-limit-redis.ts # Redis/Upstash distributed rate limiting
packages/core/templates/app/api/ # All template routes use rate limiting
plugins/*/api/ # Plugin API routes
themes/*/api/ # Theme API routes
CRITICAL: Every route.ts file that exports HTTP handlers (GET, POST, PUT, PATCH, DELETE)
MUST use the withRateLimitTier HOC wrapper.
Only exceptions:
/api/auth/** - Better Auth handles its own rate limiting/api/cron/** - Protected by Vercel cron signatures/api/webhooks/** - Protected by webhook signatures| Tier | Limit | Window | Use Case |
|------|-------|--------|----------|
| auth | 5 | 15 min | Authentication endpoints (login, register, reset password) |
| read | 200 | 1 min | GET requests, read-only operations |
| write | 50 | 1 min | POST, PUT, PATCH, DELETE operations |
| api | 100 | 1 min | General API (default tier) |
| strict | 10 | 1 hr | Sensitive operations (bulk delete, admin actions) |
// route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
// For GET (read operations)
export const GET = withRateLimitTier(async (request: NextRequest) => {
// Your handler logic
return NextResponse.json({ data: [] });
}, 'read');
// For POST (write operations)
export const POST = withRateLimitTier(async (request: NextRequest) => {
// Your handler logic
return NextResponse.json({ created: true }, { status: 201 });
}, 'write');
// For PUT/PATCH (update operations)
export const PUT = withRateLimitTier(async (request: NextRequest) => {
// Your handler logic
return NextResponse.json({ updated: true });
}, 'write');
// For DELETE
export const DELETE = withRateLimitTier(async (request: NextRequest) => {
// Your handler logic
return NextResponse.json({ deleted: true });
}, 'write');
// [id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
// Handler receives route params as second argument
export const GET = withRateLimitTier(async (
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) => {
const { id } = await params;
return NextResponse.json({ id });
}, 'read');
// Use 'strict' tier for operations like bulk delete, admin actions
export const DELETE = withRateLimitTier(async (request: NextRequest) => {
// Bulk delete or sensitive operation
return NextResponse.json({ deleted: true });
}, 'strict');
All rate-limited responses include these headers:
X-RateLimit-Limit: 200 # Max requests in window
X-RateLimit-Remaining: 150 # Requests remaining
X-RateLimit-Reset: 1705432100 # Unix timestamp when window resets
When limit is exceeded (429 response):
Retry-After: 45 # Seconds until retry allowed
{
"success": false,
"error": "Rate limit exceeded",
"message": "Too many requests. Please try again later.",
"code": "RATE_LIMIT_EXCEEDED",
"meta": {
"limit": 200,
"remaining": 0,
"resetTime": "2024-01-16T12:00:00.000Z",
"retryAfter": 45
}
}
The system uses a per-tier global limit strategy (NOT per-endpoint):
User makes requests:
GET /api/v1/products → counts against 'read' tier
GET /api/v1/orders → counts against 'read' tier
GET /api/v1/customers → counts against 'read' tier
All 3 requests count toward the SAME 200/min 'read' limit
Why global per-tier?
Rate limits are tracked by:
{tier}:apikey:{first16chars}{tier}:ip:{clientIp}This allows:
For distributed rate limiting across multiple instances, configure Redis:
# Upstash Redis (recommended for Vercel)
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=xxx
# Or standard Redis
REDIS_URL=redis://localhost:6379
Without Redis, the system falls back to in-memory rate limiting (single-instance only).
To disable rate limiting completely (useful for development, testing, or debugging):
# .env.local or .env
DISABLE_RATE_LIMITING=true
When disabled:
[RateLimit] WARNING: Rate limiting is DISABLED...WARNING: Never disable rate limiting in production environments!
Use cases for disabling:
// NEVER: Skip rate limiting on public endpoints
export const GET = async (request: NextRequest) => {
// Missing withRateLimitTier - VULNERABLE TO DDOS!
return NextResponse.json({ data: [] });
};
// NEVER: Use wrong tier (read tier for write operations)
export const POST = withRateLimitTier(async (request: NextRequest) => {
await createEntity(data); // Write operation
return NextResponse.json({ created: true });
}, 'read'); // WRONG! Should be 'write'
// NEVER: Hardcode rate limits in handlers
export const GET = async (request: NextRequest) => {
const ip = request.headers.get('x-forwarded-for');
if (requestCount[ip] > 100) { // Custom implementation - DON'T
return NextResponse.json({ error: 'Too many' }, { status: 429 });
}
// ...
};
// CORRECT: Always use the HOC
export const GET = withRateLimitTier(async (request: NextRequest) => {
return NextResponse.json({ data: [] });
}, 'read');
Before finalizing any API endpoint:
withRateLimitTierimport { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'# Check if a route has rate limiting
grep -l "withRateLimitTier" packages/core/templates/app/api/**/route.ts
# Find routes WITHOUT rate limiting (potential vulnerabilities)
grep -L "withRateLimitTier" packages/core/templates/app/api/**/route.ts
# Count rate-limited vs unprotected routes
echo "Protected:"; grep -l "withRateLimitTier" packages/core/templates/app/api/**/route.ts | wc -l
echo "Unprotected:"; grep -L "withRateLimitTier" packages/core/templates/app/api/**/route.ts | wc -l
// Cypress test example
describe('Rate Limiting', () => {
it('should return 429 after exceeding limit', () => {
// Make requests up to the limit
for (let i = 0; i < 210; i++) {
cy.request({
method: 'GET',
url: '/api/v1/products',
headers: { 'x-api-key': Cypress.env('API_KEY') },
failOnStatusCode: false
});
}
// Next request should be rate limited
cy.request({
method: 'GET',
url: '/api/v1/products',
headers: { 'x-api-key': Cypress.env('API_KEY') },
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(429);
expect(response.body.code).to.eq('RATE_LIMIT_EXCEEDED');
expect(response.headers).to.have.property('retry-after');
});
});
});
better-auth - Authentication patterns (auth tier integration)nextjs-api-development - API route development patternscypress-api - API testing with rate limit considerationsdevelopment
Zod validation patterns for this Next.js application. Covers schema definition, API validation, form integration, error formatting, and type inference. Use this skill when implementing validation for APIs, forms, or entity schemas.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
testing
Test coverage metrics and registry system for this Next.js application. Covers FEATURE_REGISTRY, FLOW_REGISTRY, TAGS_REGISTRY, and coverage metrics interpretation. Use this skill when evaluating test coverage, identifying gaps, or planning testing priorities.
development
TanStack Query (React Query) patterns for data fetching in this Next.js application. Covers useQuery, useMutation, optimistic updates, cache invalidation, and anti-patterns. Use this skill when implementing data fetching or state management with server data.