← Files ClerkARCHIVED FILE
skills/clerk-nextjs-patterns/references/middleware-strategies.md
3.65 KB · Oct 4, 2026 · 12:23 UTC
# Middleware Strategies
> **Filename:** `proxy.ts` (Next.js <=15: `middleware.ts`). The code is identical; only the filename changes.
## Public-First (marketing sites, blogs)
Protect specific routes, allow everything else:
```typescript
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/settings(.*)',
'/api/private(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) 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)(.*)',
],
};
```
## Protected-First (internal tools, dashboards)
Block everything, allow specific public routes:
```typescript
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/public(.*)',
]);
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) await auth.protect();
});
```
## Permission-Gated Routes
For B2B apps where some routes require a specific permission or role, pass a callback to `auth.protect()`. Clerk returns a 404 if the check fails:
```typescript
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isInvoiceRoute = createRouteMatcher(['/invoices(.*)']);
const isAdminRoute = createRouteMatcher(['/admin(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isInvoiceRoute(req)) {
await auth.protect((has) => has({ permission: 'org:invoices:create' }));
}
if (isAdminRoute(req)) {
await auth.protect((has) =>
has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })
);
}
});
```
Prefer permissions over roles — permissions are more granular and easier to reassign across roles in the Dashboard.
> **Core 2 ONLY (skip if current SDK):** Middleware uses synchronous `clerkMiddleware((auth, req) => { auth().protect((has) => ...) })`. Note `auth()` is called as a function (not `auth.protect`) and the callback signature is the same.
## Token-Based Protection (Machine APIs)
For routes that accept different token types (OAuth tokens, machine-to-machine tokens, API keys), pass a `token` option to `auth.protect()`:
```typescript
const isMachineApi = createRouteMatcher(['/api/machine(.*)']);
const isPublicApi = createRouteMatcher(['/api/public(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isMachineApi(req)) await auth.protect({ token: 'm2m_token' });
if (isPublicApi(req)) await auth.protect({ token: 'any' });
});
```
Token types: `'session_token'` (default, browser sessions), `'oauth_token'`, `'api_key'`, `'m2m_token'`, `'any'` (accept any valid token).
> **Core 2 ONLY (skip if current SDK):** Token-type protection requires Core 3. In Core 2, `auth().protect()` only accepts a callback (no `token` option) and only validates session tokens.
## Session Tasks
When session tasks are enabled (e.g., forced password reset, MFA setup), users may have a `pending` session status. You can handle this in middleware:
```typescript
export default clerkMiddleware(async (auth, req) => {
const { sessionStatus } = await auth();
// Redirect pending sessions to task completion page
if (sessionStatus === 'pending') {
return NextResponse.redirect(new URL('/sign-in/tasks', req.url));
}
if (isProtectedRoute(req)) await auth.protect();
});
```
> **Core 2 ONLY (skip if current SDK):** `sessionStatus` is not available. Session tasks do not exist in Core 2.
[Docs](https://clerk.com/docs/reference/nextjs/clerk-middleware)
SHA-256: a1813435a214cc32364dfd84b127e2c3dd2290a370e8f94fc8ec3f0ef4a525a0