Claude-skill-registry-data middleware-patterns
Next.js middleware patterns for protected routes, auth redirects, and URL handling. Use when working with middleware.ts, protected routes, or auth redirects. Keywords: middleware, protected routes, redirect, auth check, URL, searchParams.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry-data
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/middleware-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-data-middleware-patterns && rm -rf "$T"
manifest:
data/middleware-patterns/SKILL.mdsource content
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
// 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.
// ❌ 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:
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.
// ❌ 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
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:
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
// 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:
export async function middleware(request: NextRequest) { console.log('[Middleware]', request.nextUrl.pathname); // ... }
Check matcher config:
// 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 |
| 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 handling after middleware checkauth-patterns
- Database operations (not in middleware)supabase-patterns
- Auth middleware tests intesting-patternsauth-middleware.test.ts