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
- 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)(.*)', ], }
- 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=/
- 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 (
- 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.ts for Clerk middleware Next.js ≤15: Use middleware.ts with identical code (filename only differs) Clerk's clerkMiddleware() function is the same regardless of filename The matcher configuration 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
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
if (!user) return
return (
{user.fullName}
{user.primaryEmailAddress?.emailAddress}
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.ts to proxy.ts (location remains same: root or /src)
Keep the code identical - No functional changes needed:
// 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:
pnpm add next@latest
Verify environment variables are still in .env.local (no changes needed)
Test the migration:
pnpm dev
Troubleshooting Migration If routes aren't protected, ensure proxy.ts is in the correct location (root or /src) Check that .env.local has all required Clerk keys Clear .next cache 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:
pnpm add @vercel/mcp-adapter @clerk/mcp-tools
Create MCP route at app/[transport]/route.ts:
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-known endpoints:
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.local for 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() } })
- MCP Server Security Enable Dynamic Client Registration in Clerk Dashboard Keep .well-known endpoints public but protect all MCP tools with OAuth Use acceptsToken: 'oauth_token' in auth() to require machine tokens OAuth tokens are free during public beta (pricing TBD) Always verify tokens with verifyClerkToken() before exposing user data
- 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/nextjs hooks only in Client Components ('use client')
- 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/nextjs updated: 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 (not middleware.ts) for Next.js 16 Location: Place proxy.ts at project root or in /src directory, NOT in app/ Re-exports: Config object must be exported from proxy.ts for matcher to work Async operations: clerkMiddleware() is async-ready; use await 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 Clerk Documentation Clerk Support Clerk Discord Community Clerk Changelog Clerk Feedback