--- 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](https://clerk.com/docs/nextjs/getting-started/quickstart) - [Clerk MCP Server Guide](https://clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server) - [Clerk Next.js SDK Reference](https://clerk.com/docs/reference/nextjs/overview) - [clerkMiddleware() Reference](https://clerk.com/docs/reference/nextjs/clerk-middleware) - [Reading User Data](https://clerk.com/docs/nextjs/guides/users/reading) - [Protecting Routes](https://clerk.com/docs/reference/nextjs/clerk-middleware) - [OAuth Token Verification](https://clerk.com/docs/nextjs/guides/development/verifying-oauth-access-tokens) - [Clerk Dashboard](https://dashboard.clerk.com/) - [@vercel/mcp-adapter](https://github.com/vercel/mcp-adapter) - [@clerk/mcp-tools](https://github.com/clerk/mcp-tools) - [MCP Example Repository](https://github.com/clerk/mcp-nextjs-example) ## Quick Start ### 1. Install Dependencies (Using pnpm) ```bash 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`: ```typescript // 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: ```env 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 ```typescript // 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 (
{children}
) } ``` ### 5. Run Your App ```bash 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: ```typescript // 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
Protected content for {userId}
} ``` Or protect all routes in `proxy.ts`: ```typescript 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: ```typescript // 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: ```typescript // app/components/user-profile.tsx 'use client' import { useUser } from '@clerk/nextjs' export function UserProfile() { const { user, isLoaded } = useUser() if (!isLoaded) return
Loading...
if (!user) return
Not signed in
return (

{user.fullName}

{user.primaryEmailAddress?.emailAddress}

) } ``` Or in server components/actions: ```typescript // 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 1. **Rename the file** from `middleware.ts` to `proxy.ts` (location remains same: root or `/src`) 2. **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 = { ... } ``` 3. **Update Next.js version**: ```bash pnpm add next@latest ``` 4. **Verify environment variables** are still in `.env.local` (no changes needed) 5. **Test the migration**: ```bash 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](references/CLERK_MCP_SERVER_SETUP.md) for complete MCP server integration. ### Quick MCP Setup Summary 1. **Install MCP dependencies**: ```bash pnpm add @vercel/mcp-adapter @clerk/mcp-tools ``` 2. **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 } ``` 3. **Expose OAuth metadata endpoints** (see references for complete setup) 4. **Update proxy.ts** to exclude `.well-known` endpoints: ```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() }) ``` 5. **Enable Dynamic Client Registration** in [Clerk Dashboard](https://dashboard.clerk.com/~/oauth-applications) ## 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 ```typescript // 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-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 ### 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/nextjs` hooks 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/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: ```typescript // 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: ```bash DEBUG_CLERK=1 pnpm dev ``` ## Related Skills - **[mcp-server-skills](../mcp-server-skills/SKILL.md)**: General MCP server patterns with Vercel adapter - **[nextjs16-skills](../nextjs16-skills/SKILL.MD)**: Next.js 16 features, breaking changes, and best practices - **[authjs-skills](../authjs-skills/SKILL.md)**: Alternative authentication using Auth.js (Auth0, GitHub, etc.) ## Resources - [Clerk Documentation](https://clerk.com/docs) - [Clerk Support](https://clerk.com/contact/support) - [Clerk Discord Community](https://clerk.com/discord) - [Clerk Changelog](https://clerk.com/changelog) - [Clerk Feedback](https://feedback.clerk.com/roadmap)