Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Advanced Clerk auth patterns for Next.js: Server Actions, middleware, caching, and App Router integration.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: clerk-nextjs-patterns3description: Advanced Next.js patterns - middleware, Server Actions, caching with4Clerk.5license: MIT6allowed-tools: WebFetch7compatibility: 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.8metadata:9author: clerk10version: 2.2.011---1213# Next.js Patterns1415> **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.1617For basic setup, see `clerk-setup` skill.1819## What Do You Need?2021| Task | Reference |22|------|-----------|23| Server vs client auth (`auth()` vs hooks) | references/server-vs-client.md |24| Configure middleware (public-first vs protected-first) | references/middleware-strategies.md |25| Protect Server Actions | references/server-actions.md |26| API route auth (401 vs 403) | references/api-routes.md |27| Cache auth data (user-scoped caching) | references/caching-auth.md |2829## References3031| Reference | Description |32|-----------|-------------|33| `references/server-vs-client.md` | `await auth()` vs hooks |34| `references/middleware-strategies.md` | Public-first vs protected-first, `proxy.ts` (Next.js <=15: `middleware.ts`) |35| `references/server-actions.md` | Protect mutations |36| `references/api-routes.md` | 401 vs 403 |37| `references/caching-auth.md` | User-scoped caching |3839## Mental Model4041Server vs Client = different auth APIs:42- **Server**: `await auth()` from `@clerk/nextjs/server` (async!)43- **Client**: `useAuth()` hook from `@clerk/nextjs` (sync)4445Never mix them. Server Components use server imports, Client Components use hooks.4647Key properties from `auth()`:48- `isAuthenticated` — boolean, replaces the `!!userId` pattern49- `sessionStatus` — `'active'` | `'pending'`, for detecting incomplete session tasks50- `userId`, `orgId`, `orgSlug`, `has()`, `protect()` — unchanged5152> **Core 2 ONLY (skip if current SDK):** `isAuthenticated` and `sessionStatus` are not available. Check `!!userId` instead.5354## Minimal Pattern5556```typescript57// Server Component58import { auth } from '@clerk/nextjs/server'5960export default async function Page() {61const { isAuthenticated, userId } = await auth() // MUST await!62if (!isAuthenticated) return <p>Not signed in</p>63return <p>Hello {userId}</p>64}65```6667> **Core 2 ONLY (skip if current SDK):** `isAuthenticated` is not available. Use `if (!userId)` instead.6869### Conditional Rendering with `<Show>`7071For client-side conditional rendering based on auth state. `<Show>` covers both authentication checks and authorization (feature, plan, role, permission) in one component.7273**Authentication check:**7475```tsx76import { Show } from '@clerk/nextjs'7778<Show when="signed-in" fallback={<p>Please sign in</p>}>79<Dashboard />80</Show>81```8283**Authorization checks (B2B):**8485```tsx86// Feature-based (preferred — features can move between plans without redeploy)87<Show when={{ feature: 'analytics' }} fallback={<UpgradePrompt />}>88<AnalyticsDashboard />89</Show>9091// Permission-based (preferred over role-based for granular access)92<Show when={{ permission: 'org:invoices:create' }}>93<NewInvoiceButton />94</Show>9596// Plan-based (tier-level gating)97<Show when={{ plan: 'pro' }}>98<ProFeatures />99</Show>100101// Role-based (use sparingly — prefer permission)102<Show when={{ role: 'org:admin' }}>103<AdminPanel />104</Show>105```106107**Callback for complex logic:**108109```tsx110<Show when={(has) => has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })}>111<BillingActions />112</Show>113```114115> **Core 2 ONLY (skip if current SDK):** `<Show>` does not exist. For authentication, use `<SignedIn>` and `<SignedOut>`. For authorization (role / permission), use `<Protect>` 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.116117## Common Pitfalls118119| Symptom | Cause | Fix |120|---------|-------|-----|121| `undefined` userId in Server Component | Missing `await` | `await auth()` not `auth()` |122| Auth not working on API routes | Missing matcher | Add `'/(api|trpc)(.*)'` to `proxy.ts` (Next.js <=15: `middleware.ts`) |123| Cache returns wrong user's data | Missing userId in key | Include `userId` in `unstable_cache` key |124| Mutations bypass auth | Unprotected Server Action | Check `auth()` at start of action |125| Wrong HTTP error code | Confused 401/403 | 401 = not signed in, 403 = no permission |126127## Session Tokens & Custom JWTs128129### getToken() for external APIs130131Pass a custom JWT to third-party services (Hasura, Supabase, etc.) using JWT templates defined in the Clerk dashboard.132133**Server-side (Server Component or Route Handler)**:134135```typescript136import { auth } from '@clerk/nextjs/server'137138export default async function Page() {139const { getToken } = await auth()140const token = await getToken({ template: 'hasura' })141if (!token) return <p>Not authenticated</p>142143const res = await fetch('https://api.example.com/graphql', {144headers: { Authorization: `Bearer ${token}` },145})146const data = await res.json()147return <pre>{JSON.stringify(data)}</pre>148}149```150151**Client-side (Client Component)**:152153```typescript154'use client'155import { useAuth } from '@clerk/nextjs'156157export function DataFetcher() {158const { getToken } = useAuth()159160async function fetchData() {161const token = await getToken({ template: 'supabase' })162if (!token) return163164const res = await fetch('https://api.example.com/data', {165headers: { Authorization: `Bearer ${token}` },166})167return res.json()168}169170return <button onClick={fetchData}>Fetch</button>171}172```173174`getToken()` returns `null` when the user is not authenticated — always null-check before use.175176### useSession() for session data177178Access session metadata in client components:179180```typescript181'use client'182import { useSession } from '@clerk/nextjs'183184export function SessionInfo() {185const { session } = useSession()186if (!session) return null187188return (189<p>190Session {session.id} — last active: {session.lastActiveAt.toISOString()}191</p>192)193}194```195196### Manual JWT verification (no Clerk middleware)197198For standalone API servers that receive Clerk session tokens from the `Authorization` header or the `__session` cookie (same-origin).199200**Using `@clerk/backend` `verifyToken`** (recommended):201202```typescript203import { verifyToken } from '@clerk/backend'204205const token = req.headers.authorization?.replace('Bearer ', '')206if (!token) return res.status(401).json({ error: 'No token' })207208try {209const claims = await verifyToken(token, {210jwtKey: process.env.CLERK_JWT_KEY,211})212// claims.sub = userId213} catch {214return res.status(401).json({ error: 'Invalid token' })215}216```217218**Using `jsonwebtoken`** (when you can't use `@clerk/backend`):219220```typescript221import jwt from 'jsonwebtoken'222223const publicKey = process.env.CLERK_PEM_PUBLIC_KEY!.replace(/\\n/g, '\n')224const token = req.headers.authorization?.replace('Bearer ', '')225if (!token) return res.status(401).json({ error: 'No token' })226227try {228const claims = jwt.verify(token, publicKey, { algorithms: ['RS256'] }) as jwt.JwtPayload229// Manually check exp and nbf (jsonwebtoken does this automatically, but verify azp if needed)230// claims.sub = userId231} catch {232return res.status(401).json({ error: 'Invalid or expired token' })233}234```235236Token sources:237- **Same-origin requests**: `__session` cookie (Clerk sets this automatically)238- **Cross-origin / mobile / API-to-API**: `Authorization: Bearer <token>` header239240> **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.241242## See Also243244- `clerk-setup` - Initial Clerk install245- `clerk-orgs` - B2B patterns (active org, role/permission gating)246- `clerk-billing` - Plan and feature entitlements with `has()`247- `clerk-webhooks` - Sync user/org events to your database248- `clerk-custom-ui` - Theming and customization for built-in components249250## Docs251252[Next.js SDK](https://clerk.com/docs/reference/nextjs/overview)253