Agent skill

middleware-patterns

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/middleware-patterns

SKILL.md

Middleware Patterns Skill

Overview

Next.js middleware runs before every request. StepLeague uses it for auth protection and redirects.


Middleware Location

src/
└── middleware.ts   ← Runs on every request matching config

Protected Routes Configuration

typescript
// src/middleware.ts

export const config = {
  matcher: [
    // Protected routes - require auth
    '/dashboard/:path*',
    '/league/:path*',
    '/settings/:path*',
    '/submit-steps/:path*',
    '/claim/:path*',
    
    // Admin routes
    '/admin/:path*',
  ],
};

Pattern 1: Auth Redirect with Full URL Preservation

Problem: Users lose query params after sign-in redirect.

typescript
// ❌ WRONG - Loses query params
const redirectUrl = new URL('/sign-in', request.url);
redirectUrl.searchParams.set('redirect', pathname);

// ✅ CORRECT - Preserves full URL
const redirectUrl = new URL('/sign-in', request.url);
const fullOriginalUrl = pathname + (request.nextUrl.search || '');
redirectUrl.searchParams.set('redirect', fullOriginalUrl);

Full Implementation:

typescript
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Check for auth token in cookies
  const hasAuthToken = request.cookies.has('sb-access-token') ||
                       request.cookies.getAll().some(c => c.name.includes('-auth-token'));
  
  if (!hasAuthToken) {
    // Preserve full URL including query params
    const signInUrl = new URL('/sign-in', request.url);
    const fullPath = pathname + (request.nextUrl.search || '');
    signInUrl.searchParams.set('redirect', fullPath);
    
    return NextResponse.redirect(signInUrl);
  }
  
  return NextResponse.next();
}

Pattern 2: Edge Runtime Compatibility

Critical: Don't use @supabase/supabase-js in middleware - it's not Edge-compatible.

typescript
// ❌ WRONG - Breaks Edge runtime
import { createServerSupabaseClient } from '@/lib/supabase/server';

export async function middleware(request: NextRequest) {
  const supabase = await createServerSupabaseClient();  // Crashes!
}

// ✅ CORRECT - Check cookies directly
export async function middleware(request: NextRequest) {
  const hasAuth = request.cookies.has('sb-access-token');
  // Simple check, no Supabase import
}

Pattern 3: Route-Based Logic

typescript
export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Public routes - skip auth
  if (pathname.startsWith('/api/public') || 
      pathname === '/sign-in' ||
      pathname === '/sign-up') {
    return NextResponse.next();
  }
  
  // Admin routes - require superadmin
  if (pathname.startsWith('/admin')) {
    // Note: Full superadmin check happens in API/page
    // Middleware just ensures auth exists
    if (!hasAuth) {
      return NextResponse.redirect(new URL('/sign-in', request.url));
    }
  }
  
  // Protected routes - require any auth
  if (!hasAuth) {
    return redirectToSignIn(request);
  }
  
  return NextResponse.next();
}

Pattern 4: Header Injection

Add custom headers for downstream use:

typescript
export async function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Add pathname for layouts
  response.headers.set('x-pathname', request.nextUrl.pathname);
  
  // Add timestamp for debugging
  response.headers.set('x-middleware-ts', Date.now().toString());
  
  return response;
}

StepLeague Middleware Structure

typescript
// src/middleware.ts

import { NextResponse, type NextRequest } from 'next/server';

const PROTECTED_PATHS = [
  '/dashboard',
  '/league',
  '/settings',
  '/submit-steps',
  '/claim',
  '/admin',
];

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Check if route needs protection
  const isProtected = PROTECTED_PATHS.some(p => pathname.startsWith(p));
  
  if (!isProtected) {
    return NextResponse.next();
  }
  
  // Check for auth cookie
  const authCookie = request.cookies.getAll()
    .find(c => c.name.includes('-auth-token'));
  
  if (!authCookie) {
    const signInUrl = new URL('/sign-in', request.url);
    const fullPath = pathname + (request.nextUrl.search || '');
    signInUrl.searchParams.set('redirect', fullPath);
    return NextResponse.redirect(signInUrl);
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

Debugging Middleware

Check if middleware runs:

typescript
export async function middleware(request: NextRequest) {
  console.log('[Middleware]', request.nextUrl.pathname);
  // ...
}

Check matcher config:

typescript
// Test your matcher at: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher

Common Issues

Issue Cause Fix
Middleware not running Matcher excludes path Update config.matcher
Redirect loops Signin page matched Add signin to public paths
Missing query params Not preserving search Use pathname + search
Edge runtime error Using Node.js APIs Use only Edge-compatible code

Key File

File: src/middleware.ts

This is the actual middleware implementation. See this file for the current protected paths and auth logic.


Related Skills

  • auth-patterns - Auth handling after middleware check
  • supabase-patterns - Database operations (not in middleware)
  • testing-patterns - Auth middleware tests in auth-middleware.test.ts

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results