Files
gh-jezweb-claude-skills-ski…/templates/nextjs/middleware.ts
2025-11-30 08:24:03 +08:00

144 lines
3.6 KiB
TypeScript

/**
* Next.js Middleware with Clerk Authentication
*
* This middleware protects routes using Clerk's clerkMiddleware.
* Place this file in the root of your Next.js project.
*
* Dependencies:
* - @clerk/nextjs@^6.33.3
*
* CRITICAL (v6 Breaking Change):
* - auth.protect() is now async - must use await
* - Source: https://clerk.com/changelog/2024-10-22-clerk-nextjs-v6
*/
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
/**
* Define public routes (routes that don't require authentication)
*
* Glob patterns supported:
* - '/path' - exact match
* - '/path(.*)' - path and all sub-paths
* - '/api/public/*' - wildcard
*/
const isPublicRoute = createRouteMatcher([
'/', // Homepage
'/sign-in(.*)', // Sign-in page and sub-paths
'/sign-up(.*)', // Sign-up page and sub-paths
'/api/public(.*)', // Public API routes
'/api/webhooks(.*)', // Webhook endpoints
'/about', // Static pages
'/pricing',
'/contact',
])
/**
* Alternative: Define protected routes instead
*
* Uncomment this pattern if you prefer to explicitly protect
* specific routes rather than inverting the logic:
*/
/*
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/profile(.*)',
'/admin(.*)',
'/api/private(.*)',
])
export default clerkMiddleware(async (auth, request) => {
if (isProtectedRoute(request)) {
await auth.protect()
}
})
*/
/**
* Default Pattern: Protect all routes except public ones
*
* CRITICAL:
* - auth.protect() MUST be awaited (async in v6)
* - Without await, route protection will not work
*/
export default clerkMiddleware(async (auth, request) => {
if (!isPublicRoute(request)) {
await auth.protect()
}
})
/**
* Matcher Configuration
*
* Defines which paths run middleware.
* This is the recommended configuration from Clerk.
*/
export const config = {
matcher: [
// Skip Next.js internals and static files
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
}
/**
* Advanced: Role-Based Protection
*
* Protect routes based on user role or organization membership:
*/
/*
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
const isOrgRoute = createRouteMatcher(['/org(.*)'])
export default clerkMiddleware(async (auth, request) => {
// Admin routes require 'admin' role
if (isAdminRoute(request)) {
await auth.protect((has) => {
return has({ role: 'admin' })
})
}
// Organization routes require organization membership
if (isOrgRoute(request)) {
await auth.protect((has) => {
return has({ permission: 'org:member' })
})
}
// All other routes use default protection
if (!isPublicRoute(request)) {
await auth.protect()
}
})
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
}
*/
/**
* Troubleshooting:
*
* 1. Routes not protected?
* - Ensure auth.protect() is awaited
* - Check matcher configuration includes your routes
* - Verify middleware.ts is in project root
*
* 2. Infinite redirects?
* - Ensure sign-in/sign-up routes are in isPublicRoute
* - Check NEXT_PUBLIC_CLERK_SIGN_IN_URL in .env.local
*
* 3. API routes returning HTML?
* - Verify '/(api|trpc)(.*)' is in matcher
* - Check API routes are not in isPublicRoute if protected
*
* Official Docs: https://clerk.com/docs/reference/nextjs/clerk-middleware
*/