typescriptadvanced

Middleware Chain Pattern

Compose multiple middleware functions for auth, rate limiting, and geolocation in Next.js middleware.

typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

type MiddlewareFn = (
  req: NextRequest,
  res: NextResponse,
) => NextResponse | Promise<NextResponse>;

// Compose middleware functions
function chain(...middlewares: MiddlewareFn[]) {
  return async (req: NextRequest): Promise<NextResponse> => {
    let response = NextResponse.next();
    for (const mw of middlewares) {
      response = await mw(req, response);
      // If middleware redirected or returned error, stop chain
      if (response.status >= 300) return response;
    }
    return response;
  };
}

// Auth middleware
const withAuth: MiddlewareFn = (req, res) => {
  const token = req.cookies.get('session')?.value;
  const isProtected = req.nextUrl.pathname.startsWith('/dashboard');

  if (isProtected && !token) {
    const loginUrl = new URL('/login', req.url);
    loginUrl.searchParams.set('redirect', req.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  if (token) res.headers.set('x-user-authenticated', 'true');
  return res;
};

// Rate limiting (simple in-memory)
const ipCounts = new Map<string, { count: number; reset: number }>();

const withRateLimit: MiddlewareFn = (req, res) => {
  if (!req.nextUrl.pathname.startsWith('/api')) return res;

  const ip = req.headers.get('x-forwarded-for') ?? 'unknown';
  const now = Date.now();
  const entry = ipCounts.get(ip);

  if (!entry || now > entry.reset) {
    ipCounts.set(ip, { count: 1, reset: now + 60000 });
  } else {
    entry.count++;
    if (entry.count > 60) {
      return new NextResponse('Too Many Requests', {
        status: 429,
        headers: { 'Retry-After': '60' },
      });
    }
  }

  return res;
};

// Security headers
const withSecurityHeaders: MiddlewareFn = (req, res) => {
  res.headers.set('X-Frame-Options', 'DENY');
  res.headers.set('X-Content-Type-Options', 'nosniff');
  res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
  return res;
};

// Compose and export
export const middleware = chain(
  withSecurityHeaders,
  withRateLimit,
  withAuth,
);

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Sponsored

Vercel

Use Cases

  • Auth guard middleware
  • API rate limiting
  • Security header injection

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.