Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add gocallum/nextjs16-agent-skills --skill "clerk-nextjs-skills"
Install specific skill from multi-skill repository
# Description
Clerk authentication for Next.js 16 (App Router only) with proxy.ts setup, migration from middleware.ts, environment configuration, and MCP server integration.
# SKILL.md
name: clerk-nextjs-skills
description: Clerk authentication for Next.js 16 (App Router only) with proxy.ts setup, migration from middleware.ts, environment configuration, and MCP server integration.
Links
- Clerk Next.js Quickstart
- Clerk MCP Server Guide
- Clerk Next.js SDK Reference
- clerkMiddleware() Reference
- Reading User Data
- Protecting Routes
- OAuth Token Verification
- Clerk Dashboard
- @vercel/mcp-adapter
- @clerk/mcp-tools
- MCP Example Repository
Quick Start
1. Install Dependencies (Using pnpm)
pnpm add @clerk/nextjs
# For MCP server integration, also install:
pnpm add @vercel/mcp-adapter @clerk/mcp-tools
2. Create proxy.ts (Next.js 16)
The proxy.ts file replaces middleware.ts from Next.js 15. Create it at the root or in /src:
// proxy.ts (or src/proxy.ts)
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
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)(.*)',
],
}
3. Set Environment Variables
Create .env.local in your project root:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_publishable_key_here
CLERK_SECRET_KEY=your_secret_key_here
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/
4. Add ClerkProvider to Layout
// app/layout.tsx
import {
ClerkProvider,
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<ClerkProvider>
<html lang="en">
<body>
<header className="flex justify-end items-center p-4 gap-4 h-16">
<SignedOut>
<SignInButton />
<SignUpButton />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</header>
{children}
</body>
</html>
</ClerkProvider>
)
}
5. Run Your App
pnpm dev
Visit http://localhost:3000 and click "Sign Up" to create your first user.
Key Concepts
proxy.ts vs middleware.ts
- Next.js 16 (App Router): Use
proxy.tsfor Clerk middleware - Next.js โค15: Use
middleware.tswith identical code (filename only differs) - Clerk's
clerkMiddleware()function is the same regardless of filename - The
matcherconfiguration ensures proper route handling and performance
Protecting Routes
By default, clerkMiddleware() does not protect routesโall are public. Use auth.protect() to require authentication:
// Protect specific route
import { auth } from '@clerk/nextjs/server'
export default async function Page() {
const { userId } = await auth()
if (!userId) {
// Redirect handled by clerkMiddleware
}
return <div>Protected content for {userId}</div>
}
Or protect all routes in proxy.ts:
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware(async (auth, req) => {
await auth.protect()
})
Environment Variable Validation
Check for required Clerk keys before runtime:
// lib/clerk-config.ts
export function validateClerkEnv() {
const required = [
'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
'CLERK_SECRET_KEY',
]
const missing = required.filter(key => !process.env[key])
if (missing.length > 0) {
throw new Error(`Missing required Clerk environment variables: ${missing.join(', ')}`)
}
}
Accessing User Data
Use Clerk hooks in client components:
// app/components/user-profile.tsx
'use client'
import { useUser } from '@clerk/nextjs'
export function UserProfile() {
const { user, isLoaded } = useUser()
if (!isLoaded) return <div>Loading...</div>
if (!user) return <div>Not signed in</div>
return (
<div>
<h1>{user.fullName}</h1>
<p>{user.primaryEmailAddress?.emailAddress}</p>
</div>
)
}
Or in server components/actions:
// app/actions.ts
'use server'
import { auth, clerkClient } from '@clerk/nextjs/server'
export async function getUserData() {
const { userId } = await auth()
if (!userId) {
throw new Error('Unauthorized')
}
const clerk = await clerkClient()
const user = await clerk.users.getUser(userId)
return user
}
Migrating from middleware.ts (Next.js 15) to proxy.ts (Next.js 16)
Step-by-Step Migration
-
Rename the file from
middleware.tstoproxy.ts(location remains same: root or/src) -
Keep the code identical - No functional changes needed:
```typescript
// Before (middleware.ts)
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
export const config = { ... }
// After (proxy.ts) - Same code
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
export const config = { ... }
```
-
Update Next.js version:
bash pnpm add next@latest -
Verify environment variables are still in
.env.local(no changes needed) -
Test the migration:
bash pnpm dev
Troubleshooting Migration
- If routes aren't protected, ensure
proxy.tsis in the correct location (root or/src) - Check that
.env.localhas all required Clerk keys - Clear
.nextcache if middleware changes don't take effect:rm -rf .next && pnpm dev - Verify Next.js version is 16.0+:
pnpm list next
Building an MCP Server with Clerk
See CLERK_MCP_SERVER_SETUP.md for complete MCP server integration.
Quick MCP Setup Summary
-
Install MCP dependencies:
bash pnpm add @vercel/mcp-adapter @clerk/mcp-tools -
Create MCP route at
app/[transport]/route.ts:
```typescript
import { verifyClerkToken } from '@clerk/mcp-tools/next'
import { createMcpHandler, withMcpAuth } from '@vercel/mcp-adapter'
import { auth, clerkClient } from '@clerk/nextjs/server'
const clerk = await clerkClient()
const handler = createMcpHandler((server) => {
server.tool(
'get-clerk-user-data',
'Gets data about the Clerk user that authorized this request',
{},
async (_, { authInfo }) => {
const userId = authInfo!.extra!.userId! as string
const userData = await clerk.users.getUser(userId)
return {
content: [{ type: 'text', text: JSON.stringify(userData) }],
}
},
)
})
const authHandler = withMcpAuth(
handler,
async (_, token) => {
const clerkAuth = await auth({ acceptsToken: 'oauth_token' })
return verifyClerkToken(clerkAuth, token)
},
{
required: true,
resourceMetadataPath: '/.well-known/oauth-protected-resource/mcp',
},
)
export { authHandler as GET, authHandler as POST }
```
-
Expose OAuth metadata endpoints (see references for complete setup)
-
Update proxy.ts to exclude
.well-knownendpoints:
```typescript
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher([
'/.well-known/oauth-authorization-server(.)',
'/.well-known/oauth-protected-resource(.)',
])
export default clerkMiddleware(async (auth, req) => {
if (isPublicRoute(req)) return
await auth.protect()
})
```
- Enable Dynamic Client Registration in Clerk Dashboard
Best Practices
1. Environment Variable Management
- Always use
.env.localfor development (never commit sensitive keys) - Validate environment variables on application startup
- Use
NEXT_PUBLIC_prefix ONLY for non-sensitive keys that are safe to expose - For production, set environment variables in your deployment platform (Vercel, etc.)
2. Route Protection Strategies
// Option A: Protect all routes
export default clerkMiddleware(async (auth, req) => {
await auth.protect()
})
// Option B: Protect specific routes
import { createRouteMatcher } from '@clerk/nextjs/server'
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/api/user(.*)'])
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect()
}
})
// Option C: Public routes with opt-in protection
const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)'])
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) {
await auth.protect()
}
})
3. MCP Server Security
- Enable Dynamic Client Registration in Clerk Dashboard
- Keep
.well-knownendpoints public but protect all MCP tools with OAuth - Use
acceptsToken: 'oauth_token'inauth()to require machine tokens - OAuth tokens are free during public beta (pricing TBD)
- Always verify tokens with
verifyClerkToken()before exposing user data
4. Performance & Caching
- Use
clerkClient()for server-side user queries (cached automatically) - Leverage React Server Components for secure user data access
- Cache user data when possible to reduce API calls
- Use
@clerk/nextjshooks only in Client Components ('use client')
5. Production Deployment
- Set all environment variables in your deployment platform
- Use Clerk's production instance keys (not development keys)
- Test authentication flow in staging environment before production
- Monitor Clerk Dashboard for authentication errors
- Keep
@clerk/nextjsupdated:pnpm update @clerk/nextjs
Troubleshooting
Issues & Solutions
| Issue | Solution |
|---|---|
| "Missing environment variables" | Ensure .env.local has NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY |
| Middleware not protecting routes | Verify proxy.ts is in root or /src directory, not in app/ |
| Sign-in/sign-up pages not working | Check NEXT_PUBLIC_CLERK_SIGN_IN_URL and NEXT_PUBLIC_CLERK_SIGN_UP_URL in .env.local |
| User data returns null | Ensure user is authenticated: check userId is not null before calling getUser() |
| MCP server OAuth fails | Enable Dynamic Client Registration in Clerk Dashboard OAuth Applications |
| Changes not taking effect | Clear .next cache: rm -rf .next and restart pnpm dev |
| "proxy.ts" not recognized | Verify Next.js version is 16.0+: pnpm list next |
Common Next.js 16 Gotchas
- File naming: Must be
proxy.ts(notmiddleware.ts) for Next.js 16 - Location: Place
proxy.tsat project root or in/srcdirectory, NOT inapp/ - Re-exports: Config object must be exported from
proxy.tsfor matcher to work - Async operations:
clerkMiddleware()is async-ready; useawait auth.protect()for route protection
Debug Mode
Enable debug logging:
// proxy.ts
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware((auth, req) => {
if (process.env.DEBUG_CLERK) {
console.log('Request URL:', req.nextUrl.pathname)
console.log('User ID:', auth.sessionClaims?.sub)
}
})
Run with debug:
DEBUG_CLERK=1 pnpm dev
Related Skills
- mcp-server-skills: General MCP server patterns with Vercel adapter
- nextjs16-skills: Next.js 16 features, breaking changes, and best practices
- authjs-skills: Alternative authentication using Auth.js (Auth0, GitHub, etc.)
Resources
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.