Nextjs Middleware Creator
Commands nextjs-vercel 293
npx claude-code-templates@latest --command nextjs-vercel/nextjs-middleware-creator Content
Next.js Middleware Creator
Middleware Type: $ARGUMENTS
Current Project Analysis
Project Structure
- Next.js config: @next.config.js
- Existing middleware: @middleware.ts or @middleware.js (if exists)
- App directory: @app/ (if App Router)
- Auth configuration: @auth.config.ts or @lib/auth/ (if exists)
Framework Detection
- Package.json: @package.json
- TypeScript config: @tsconfig.json (if exists)
- Authentication libraries: Detect NextAuth.js, Auth0, or custom auth
Middleware Implementation Strategy
1. Middleware File Structure
Create comprehensive middleware at project root:
middleware.ts # Main middleware file
lib/middleware/ # Middleware utilities
├── auth.ts # Authentication middleware
├── rateLimit.ts # Rate limiting logic
├── redirects.ts # Redirect rules
├── rewrites.ts # URL rewriting
├── cors.ts # CORS handling
├── security.ts # Security headers
└── types.ts # TypeScript types2. Base Middleware Template
typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { authMiddleware } from './lib/middleware/auth';
import { rateLimitMiddleware } from './lib/middleware/rateLimit';
import { securityMiddleware } from './lib/middleware/security';
import { redirectMiddleware } from './lib/middleware/redirects';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Apply security headers first
let response = await securityMiddleware(request);
// Apply rate limiting
const rateLimitResult = await rateLimitMiddleware(request);
if (rateLimitResult) return rateLimitResult;
// Handle authentication for protected routes
if (isProtectedRoute(pathname)) {
const authResult = await authMiddleware(request);
if (authResult) return authResult;
}
// Handle redirects
const redirectResult = await redirectMiddleware(request);
if (redirectResult) return redirectResult;
// Apply additional headers to response
if (response) {
return response;
}
return NextResponse.next();
}
function isProtectedRoute(pathname: string): boolean {
const protectedPaths = ['/dashboard', '/admin', '/api/protected'];
return protectedPaths.some(path => pathname.startsWith(path));
}
export const config = {
matcher: [
// Match all request paths except static files and images
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};Middleware Components
1. Authentication Middleware
typescript
// lib/middleware/auth.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
const JWT_SECRET = new TextEncoder().encode(
process.env.JWT_SECRET || 'your-secret-key'
);
export async function authMiddleware(request: NextRequest) {
try {
// Get token from cookies or Authorization header
const token = request.cookies.get('auth-token')?.value ||
request.headers.get('authorization')?.replace('Bearer ', '');
if (!token) {
return redirectToLogin(request);
}
// Verify JWT token
const { payload } = await jwtVerify(token, JWT_SECRET);
// Add user info to headers for downstream use
const response = NextResponse.next();
response.headers.set('x-user-id', payload.sub as string);
response.headers.set('x-user-role', payload.role as string);
return response;
} catch (error) {
console.error('Auth middleware error:', error);
return redirectToLogin(request);
}
}
function redirectToLogin(request: NextRequest) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('callbackUrl', request.url);
return NextResponse.redirect(loginUrl);
}
// Role-based access control
export function requireRole(allowedRoles: string[]) {
return async function roleMiddleware(request: NextRequest) {
const userRole = request.headers.get('x-user-role');
if (!userRole || !allowedRoles.includes(userRole)) {
return new NextResponse('Forbidden', { status: 403 });
}
return NextResponse.next();
};
}2. Rate Limiting Middleware
typescript
// lib/middleware/rateLimit.ts
import { NextRequest, NextResponse } from 'next/server';
// Simple in-memory store (use Redis in production)
const requestCounts = new Map<string, { count: number; resetTime: number }>();
interface RateLimitConfig {
windowMs: number; // Time window in milliseconds
maxRequests: number; // Max requests per window
keyGenerator?: (request: NextRequest) => string;
}
const defaultConfig: RateLimitConfig = {
windowMs: 15 * 60 * 1000, // 15 minutes
maxRequests: 100, // 100 requests per 15 minutes
};
export async function rateLimitMiddleware(
request: NextRequest,
config: RateLimitConfig = defaultConfig
) {
const key = config.keyGenerator
? config.keyGenerator(request)
: getClientIP(request);
const now = Date.now();
const clientData = requestCounts.get(key);
// Reset window if expired
if (!clientData || now > clientData.resetTime) {
requestCounts.set(key, {
count: 1,
resetTime: now + config.windowMs
});
return null; // Allow request
}
// Increment counter
clientData.count++;
// Check if limit exceeded
if (clientData.count > config.maxRequests) {
const resetTime = Math.ceil((clientData.resetTime - now) / 1000);
return new NextResponse('Rate limit exceeded', {
status: 429,
headers: {
'X-RateLimit-Limit': config.maxRequests.toString(),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': resetTime.toString(),
'Retry-After': resetTime.toString(),
},
});
}
return null; // Allow request
}
function getClientIP(request: NextRequest): string {
return request.headers.get('x-forwarded-for') ||
request.headers.get('x-real-ip') ||
request.ip ||
'unknown';
}
// API-specific rate limiting
export const apiRateLimit = (request: NextRequest) =>
rateLimitMiddleware(request, {
windowMs: 60 * 1000, // 1 minute
maxRequests: 60, // 60 requests per minute
keyGenerator: (req) => `api:${getClientIP(req)}`,
});3. Security Headers Middleware
typescript
// lib/middleware/security.ts
import { NextRequest, NextResponse } from 'next/server';
export async function securityMiddleware(request: NextRequest) {
const response = NextResponse.next();
// Security headers
const securityHeaders = {
// XSS Protection
'X-XSS-Protection': '1; mode=block',
// Content Type Options
'X-Content-Type-Options': 'nosniff',
// Frame Options
'X-Frame-Options': 'DENY',
// HSTS
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
// Referrer Policy
'Referrer-Policy': 'strict-origin-when-cross-origin',
// Permissions Policy
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
// Content Security Policy
'Content-Security-Policy': generateCSP(),
};
// Apply security headers
Object.entries(securityHeaders).forEach(([key, value]) => {
response.headers.set(key, value);
});
return response;
}
function generateCSP(): string {
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-eval' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self'",
"frame-ancestors 'none'",
];
return csp.join('; ');
}4. CORS Middleware
typescript
// lib/middleware/cors.ts
import { NextRequest, NextResponse } from 'next/server';
interface CorsOptions {
origin: string | string[] | boolean;
methods: string[];
allowedHeaders: string[];
credentials: boolean;
}
const defaultCorsOptions: CorsOptions = {
origin: true, // Allow all origins in development
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true,
};
export function corsMiddleware(options: Partial<CorsOptions> = {}) {
const config = { ...defaultCorsOptions, ...options };
return function cors(request: NextRequest) {
const response = NextResponse.next();
const origin = request.headers.get('origin');
// Handle preflight requests
if (request.method === 'OPTIONS') {
return handlePreflight(request, config);
}
// Set CORS headers
if (shouldAllowOrigin(origin, config.origin)) {
response.headers.set('Access-Control-Allow-Origin', origin || '*');
}
if (config.credentials) {
response.headers.set('Access-Control-Allow-Credentials', 'true');
}
response.headers.set(
'Access-Control-Allow-Methods',
config.methods.join(', ')
);
response.headers.set(
'Access-Control-Allow-Headers',
config.allowedHeaders.join(', ')
);
return response;
};
}
function handlePreflight(request: NextRequest, config: CorsOptions) {
const headers = new Headers();
const origin = request.headers.get('origin');
if (shouldAllowOrigin(origin, config.origin)) {
headers.set('Access-Control-Allow-Origin', origin || '*');
}
if (config.credentials) {
headers.set('Access-Control-Allow-Credentials', 'true');
}
headers.set('Access-Control-Allow-Methods', config.methods.join(', '));
headers.set('Access-Control-Allow-Headers', config.allowedHeaders.join(', '));
headers.set('Access-Control-Max-Age', '86400'); // 24 hours
return new NextResponse(null, { status: 200, headers });
}
function shouldAllowOrigin(
origin: string | null,
allowedOrigin: string | string[] | boolean
): boolean {
if (allowedOrigin === true) return true;
if (allowedOrigin === false) return false;
if (typeof allowedOrigin === 'string') return origin === allowedOrigin;
if (Array.isArray(allowedOrigin)) return allowedOrigin.includes(origin || '');
return false;
}5. Redirect and Rewrite Middleware
typescript
// lib/middleware/redirects.ts
import { NextRequest, NextResponse } from 'next/server';
interface RedirectRule {
source: string | RegExp;
destination: string;
permanent?: boolean;
conditions?: (request: NextRequest) => boolean;
}
const redirectRules: RedirectRule[] = [
// Legacy URL redirects
{
source: '/old-page',
destination: '/new-page',
permanent: true,
},
// Dynamic redirects
{
source: /^\/user\/(.+)$/,
destination: '/profile/$1',
permanent: false,
},
// Conditional redirects
{
source: '/admin',
destination: '/admin/dashboard',
conditions: (request) => {
const userRole = request.headers.get('x-user-role');
return userRole === 'admin';
},
},
// Maintenance mode
{
source: /.*/,
destination: '/maintenance',
conditions: (request) => {
return process.env.MAINTENANCE_MODE === 'true' &&
!request.nextUrl.pathname.startsWith('/maintenance');
},
},
];
export async function redirectMiddleware(request: NextRequest) {
const { pathname } = request.nextUrl;
for (const rule of redirectRules) {
if (shouldApplyRule(rule, pathname, request)) {
const destination = resolveDestination(rule.destination, pathname);
const url = new URL(destination, request.url);
return NextResponse.redirect(url, {
status: rule.permanent ? 301 : 302,
});
}
}
return null; // No redirect needed
}
function shouldApplyRule(
rule: RedirectRule,
pathname: string,
request: NextRequest
): boolean {
// Check pattern match
const matches = typeof rule.source === 'string'
? pathname === rule.source
: rule.source.test(pathname);
if (!matches) return false;
// Check additional conditions
if (rule.conditions) {
return rule.conditions(request);
}
return true;
}
function resolveDestination(destination: string, pathname: string): string {
// Handle dynamic replacements
return destination.replace(/\$(\d+)/g, (match, num) => {
// Extract from regex matches
return pathname; // Simplified - would need actual regex matching
});
}6. A/B Testing Middleware
typescript
// lib/middleware/abTest.ts
import { NextRequest, NextResponse } from 'next/server';
interface ABTest {
name: string;
variants: string[];
traffic: number; // Percentage of traffic to include (0-100)
condition?: (request: NextRequest) => boolean;
}
const activeTests: ABTest[] = [
{
name: 'homepage-design',
variants: ['control', 'variant-a', 'variant-b'],
traffic: 50,
},
{
name: 'checkout-flow',
variants: ['old-checkout', 'new-checkout'],
traffic: 100,
condition: (req) => req.nextUrl.pathname.startsWith('/checkout'),
},
];
export function abTestMiddleware(request: NextRequest) {
const response = NextResponse.next();
for (const test of activeTests) {
// Check if user should be included in test
if (test.condition && !test.condition(request)) continue;
// Check traffic allocation
const userId = getUserId(request);
const hash = hashString(userId + test.name);
const bucket = hash % 100;
if (bucket >= test.traffic) continue;
// Assign variant
const variantIndex = hash % test.variants.length;
const variant = test.variants[variantIndex];
// Set cookie for consistent experience
response.cookies.set(`ab_${test.name}`, variant, {
maxAge: 30 * 24 * 60 * 60, // 30 days
httpOnly: false, // Allow client-side access
});
// Set header for server-side use
response.headers.set(`x-ab-${test.name}`, variant);
}
return response;
}
function getUserId(request: NextRequest): string {
// Get user ID from cookie, or generate anonymous ID
return request.cookies.get('user-id')?.value ||
request.headers.get('x-forwarded-for') ||
'anonymous';
}
function hashString(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash);
}Advanced Middleware Patterns
1. Middleware Composition
typescript
// lib/middleware/compose.ts
import { NextRequest, NextResponse } from 'next/server';
type MiddlewareFunction = (
request: NextRequest,
response?: NextResponse
) => NextResponse | Promise<NextResponse> | null;
export function composeMiddleware(...middlewares: MiddlewareFunction[]) {
return async function composedMiddleware(request: NextRequest) {
let response: NextResponse | null = null;
for (const middleware of middlewares) {
const result = await middleware(request, response || undefined);
if (result && result.status >= 300 && result.status < 400) {
// Handle redirects immediately
return result;
}
if (result && result.status >= 400) {
// Handle errors immediately
return result;
}
if (result) {
response = result;
}
}
return response || NextResponse.next();
};
}2. Feature Flag Middleware
typescript
// lib/middleware/featureFlags.ts
import { NextRequest, NextResponse } from 'next/server';
interface FeatureFlag {
name: string;
enabled: boolean;
percentage?: number;
userGroups?: string[];
geoRegions?: string[];
}
const featureFlags: FeatureFlag[] = [
{
name: 'new-dashboard',
enabled: true,
percentage: 25,
},
{
name: 'premium-features',
enabled: true,
userGroups: ['premium', 'admin'],
},
];
export function featureFlagMiddleware(request: NextRequest) {
const response = NextResponse.next();
const activeFlags: Record<string, boolean> = {};
for (const flag of featureFlags) {
if (!flag.enabled) {
activeFlags[flag.name] = false;
continue;
}
// Check percentage rollout
if (flag.percentage) {
const userId = getUserId(request);
const hash = hashString(userId + flag.name) % 100;
if (hash >= flag.percentage) {
activeFlags[flag.name] = false;
continue;
}
}
// Check user groups
if (flag.userGroups) {
const userRole = request.headers.get('x-user-role');
if (!userRole || !flag.userGroups.includes(userRole)) {
activeFlags[flag.name] = false;
continue;
}
}
activeFlags[flag.name] = true;
}
// Set feature flags in headers
response.headers.set('x-feature-flags', JSON.stringify(activeFlags));
return response;
}Middleware Testing
1. Unit Tests
typescript
// __tests__/middleware.test.ts
import { NextRequest } from 'next/server';
import { middleware } from '../middleware';
describe('Middleware', () => {
it('should add security headers', async () => {
const request = new NextRequest('http://localhost:3000/');
const response = await middleware(request);
expect(response.headers.get('X-Frame-Options')).toBe('DENY');
expect(response.headers.get('X-Content-Type-Options')).toBe('nosniff');
});
it('should redirect unauthenticated users from protected routes', async () => {
const request = new NextRequest('http://localhost:3000/dashboard');
const response = await middleware(request);
expect(response.status).toBe(302);
expect(response.headers.get('location')).toContain('/login');
});
});2. Integration Tests
typescript
// __tests__/middleware.integration.test.ts
describe('Middleware Integration', () => {
it('should handle complete authentication flow', async () => {
// Test login -> dashboard -> logout flow
});
it('should respect rate limiting', async () => {
// Test multiple requests hitting rate limit
});
});Deployment and Monitoring
1. Performance Monitoring
typescript
// lib/middleware/monitoring.ts
export function monitoringMiddleware(request: NextRequest) {
const start = Date.now();
return new Response(JSON.stringify({}), {
status: 200,
headers: {
'x-response-time': `${Date.now() - start}ms`,
},
});
}2. Error Handling
typescript
// lib/middleware/errorHandler.ts
export function errorHandlerMiddleware(
error: Error,
request: NextRequest
): NextResponse {
console.error('Middleware error:', error);
// Log to monitoring service
// logError(error, request);
return new NextResponse('Internal Server Error', { status: 500 });
}Generate comprehensive middleware implementation with all requested features, proper TypeScript types, and production-ready patterns.