--- name: clerk-nextjs-patterns description: Advanced Next.js patterns - middleware, Server Actions, caching with Clerk. license: MIT allowed-tools: WebFetch compatibility: Requires NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY. For manual JWT verification (standalone API servers without Clerk middleware), additionally requires CLERK_JWT_KEY or CLERK_PEM_PUBLIC_KEY. metadata: author: clerk version: 2.2.0 --- # Next.js Patterns > **Version**: Check `package.json` for the SDK version — see `clerk` skill for the version table. Core 2 differences are noted inline with `> **Core 2 ONLY (skip if current SDK):**` callouts. For basic setup, see `clerk-setup` skill. ## What Do You Need? | Task | Reference | |------|-----------| | Server vs client auth (`auth()` vs hooks) | references/server-vs-client.md | | Configure middleware (public-first vs protected-first) | references/middleware-strategies.md | | Protect Server Actions | references/server-actions.md | | API route auth (401 vs 403) | references/api-routes.md | | Cache auth data (user-scoped caching) | references/caching-auth.md | ## References | Reference | Description | |-----------|-------------| | `references/server-vs-client.md` | `await auth()` vs hooks | | `references/middleware-strategies.md` | Public-first vs protected-first, `proxy.ts` (Next.js <=15: `middleware.ts`) | | `references/server-actions.md` | Protect mutations | | `references/api-routes.md` | 401 vs 403 | | `references/caching-auth.md` | User-scoped caching | ## Mental Model Server vs Client = different auth APIs: - **Server**: `await auth()` from `@clerk/nextjs/server` (async!) - **Client**: `useAuth()` hook from `@clerk/nextjs` (sync) Never mix them. Server Components use server imports, Client Components use hooks. Key properties from `auth()`: - `isAuthenticated` — boolean, replaces the `!!userId` pattern - `sessionStatus` — `'active'` | `'pending'`, for detecting incomplete session tasks - `userId`, `orgId`, `orgSlug`, `has()`, `protect()` — unchanged > **Core 2 ONLY (skip if current SDK):** `isAuthenticated` and `sessionStatus` are not available. Check `!!userId` instead. ## Minimal Pattern ```typescript // Server Component import { auth } from '@clerk/nextjs/server' export default async function Page() { const { isAuthenticated, userId } = await auth() // MUST await! if (!isAuthenticated) return

Not signed in

return

Hello {userId}

} ``` > **Core 2 ONLY (skip if current SDK):** `isAuthenticated` is not available. Use `if (!userId)` instead. ### Conditional Rendering with `` For client-side conditional rendering based on auth state. `` covers both authentication checks and authorization (feature, plan, role, permission) in one component. **Authentication check:** ```tsx import { Show } from '@clerk/nextjs' Please sign in

}>
``` **Authorization checks (B2B):** ```tsx // Feature-based (preferred — features can move between plans without redeploy) }> // Permission-based (preferred over role-based for granular access) // Plan-based (tier-level gating) // Role-based (use sparingly — prefer permission) ``` **Callback for complex logic:** ```tsx has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })}> ``` > **Core 2 ONLY (skip if current SDK):** `` does not exist. For authentication, use `` and ``. For authorization (role / permission), use `` with the same prop names (`role`, `permission`, `condition`). Feature- and plan-based variants require Core 3. See `clerk-custom-ui` skill, `core-3/show-component.md` for the full migration table. ## Common Pitfalls | Symptom | Cause | Fix | |---------|-------|-----| | `undefined` userId in Server Component | Missing `await` | `await auth()` not `auth()` | | Auth not working on API routes | Missing matcher | Add `'/(api|trpc)(.*)'` to `proxy.ts` (Next.js <=15: `middleware.ts`) | | Cache returns wrong user's data | Missing userId in key | Include `userId` in `unstable_cache` key | | Mutations bypass auth | Unprotected Server Action | Check `auth()` at start of action | | Wrong HTTP error code | Confused 401/403 | 401 = not signed in, 403 = no permission | ## Session Tokens & Custom JWTs ### getToken() for external APIs Pass a custom JWT to third-party services (Hasura, Supabase, etc.) using JWT templates defined in the Clerk dashboard. **Server-side (Server Component or Route Handler)**: ```typescript import { auth } from '@clerk/nextjs/server' export default async function Page() { const { getToken } = await auth() const token = await getToken({ template: 'hasura' }) if (!token) return

Not authenticated

const res = await fetch('https://api.example.com/graphql', { headers: { Authorization: `Bearer ${token}` }, }) const data = await res.json() return
{JSON.stringify(data)}
} ``` **Client-side (Client Component)**: ```typescript 'use client' import { useAuth } from '@clerk/nextjs' export function DataFetcher() { const { getToken } = useAuth() async function fetchData() { const token = await getToken({ template: 'supabase' }) if (!token) return const res = await fetch('https://api.example.com/data', { headers: { Authorization: `Bearer ${token}` }, }) return res.json() } return } ``` `getToken()` returns `null` when the user is not authenticated — always null-check before use. ### useSession() for session data Access session metadata in client components: ```typescript 'use client' import { useSession } from '@clerk/nextjs' export function SessionInfo() { const { session } = useSession() if (!session) return null return (

Session {session.id} — last active: {session.lastActiveAt.toISOString()}

) } ``` ### Manual JWT verification (no Clerk middleware) For standalone API servers that receive Clerk session tokens from the `Authorization` header or the `__session` cookie (same-origin). **Using `@clerk/backend` `verifyToken`** (recommended): ```typescript import { verifyToken } from '@clerk/backend' const token = req.headers.authorization?.replace('Bearer ', '') if (!token) return res.status(401).json({ error: 'No token' }) try { const claims = await verifyToken(token, { jwtKey: process.env.CLERK_JWT_KEY, }) // claims.sub = userId } catch { return res.status(401).json({ error: 'Invalid token' }) } ``` **Using `jsonwebtoken`** (when you can't use `@clerk/backend`): ```typescript import jwt from 'jsonwebtoken' const publicKey = process.env.CLERK_PEM_PUBLIC_KEY!.replace(/\\n/g, '\n') const token = req.headers.authorization?.replace('Bearer ', '') if (!token) return res.status(401).json({ error: 'No token' }) try { const claims = jwt.verify(token, publicKey, { algorithms: ['RS256'] }) as jwt.JwtPayload // Manually check exp and nbf (jsonwebtoken does this automatically, but verify azp if needed) // claims.sub = userId } catch { return res.status(401).json({ error: 'Invalid or expired token' }) } ``` Token sources: - **Same-origin requests**: `__session` cookie (Clerk sets this automatically) - **Cross-origin / mobile / API-to-API**: `Authorization: Bearer ` header > **CRITICAL**: Always check `exp` and `nbf` claims. `verifyToken` from `@clerk/backend` handles this automatically; with raw `jsonwebtoken`, set `ignoreExpiration: false` (default) and ensure `clockTolerance` is minimal. ## See Also - `clerk-setup` - Initial Clerk install - `clerk-orgs` - B2B patterns (active org, role/permission gating) - `clerk-billing` - Plan and feature entitlements with `has()` - `clerk-webhooks` - Sync user/org events to your database - `clerk-custom-ui` - Theming and customization for built-in components ## Docs [Next.js SDK](https://clerk.com/docs/reference/nextjs/overview)