← ClerkCONTENT HISTORY
WHAT CHANGED
Update to Clerk
Snapshot Nov 6, 2026 · 00:16 UTC · version 0.3.0
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Clerk and check future changes in Following.
Up to 3 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Advanced Next.js patterns - middleware, Server Actions, caching with Clerk.",
"included_files": [
{
"relative_path": "evals/evals.json",
"size_in_bytes": 5452
},
{
"relative_path": "references/api-routes.md",
"size_in_bytes": 1462
},
{
"relative_path": "references/caching-auth.md",
"size_in_bytes": 3093
},
{
"relative_path": "references/middleware-strategies.md",
"size_in_bytes": 7946
},
{
"relative_path": "references/server-actions.md",
"size_in_bytes": 1588
},
{
"relative_path": "references/server-vs-client.md",
"size_in_bytes": 3071
},
{
"relative_path": "templates/nextjs-basic-auth/app/layout.tsx",
"size_in_bytes": 659
},
{
"relative_path": "templates/nextjs-basic-auth/app/page.tsx",
"size_in_bytes": 58
},
{
"relative_path": "templates/nextjs-basic-auth/next.config.ts",
"size_in_bytes": 155
},
{
"relative_path": "templates/nextjs-basic-auth/package.json",
"size_in_bytes": 392
},
{
"relative_path": "templates/nextjs-basic-auth/proxy.ts",
"size_in_bytes": 280
},
{
"relative_path": "templates/nextjs-basic-auth/tsconfig.json",
"size_in_bytes": 612
}
],
"name": "clerk-nextjs-patterns",
"skill_md_contents": "---\nname: clerk-nextjs-patterns\ndescription: Advanced Next.js patterns - middleware, Server Actions, caching with\n Clerk.\nlicense: MIT\nallowed-tools: WebFetch\ncompatibility: 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.\nmetadata:\n author: clerk\n version: 2.2.0\n---\n\n# Next.js Patterns\n\n> **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.\n\nFor basic setup, see `clerk-setup` skill.\n\n> **Route protection:** call `await auth.protect()` in each page, Route Handler, and Server Function. Keep `clerkMiddleware()` bare. `createRouteMatcher` is deprecated in `@clerk/nextjs` 7 and will be removed in the next major version. Don't add it. If a project already uses it, leave it working and point the user to the [migration guide](https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrate-from-create-route-matcher).\n\n## What Do You Need?\n\n| Task | Reference |\n|------|-----------|\n| Server vs client auth (`auth()` vs hooks) | references/server-vs-client.md |\n| Protect routes (check in each page, handler, and Server Function) | references/middleware-strategies.md |\n| Protect Server Actions | references/server-actions.md |\n| API route auth (401 vs 403) | references/api-routes.md |\n| Cache auth data (user-scoped caching) | references/caching-auth.md |\n| Fix `next build` errors under Cache Components | [Cache Components](#cache-components) |\n\n## References\n\n| Reference | Description |\n|-----------|-------------|\n| `references/server-vs-client.md` | `await auth()` vs hooks |\n| `references/middleware-strategies.md` | `auth.protect()` in each resource, bare `clerkMiddleware()` in `proxy.ts` (Next.js <=15: `middleware.ts`) |\n| `references/server-actions.md` | Protect mutations |\n| `references/api-routes.md` | 401 vs 403 |\n| `references/caching-auth.md` | User-scoped caching with `'use cache'` |\n\n## Mental Model\n\nServer vs Client = different auth APIs:\n- **Server**: `await auth()` from `@clerk/nextjs/server` (async!)\n- **Client**: `useAuth()` hook from `@clerk/nextjs` (sync)\n\nNever mix them. Server Components use server imports, Client Components use hooks.\n\nKey properties from `auth()`:\n- `isAuthenticated` — boolean, replaces the `!!userId` pattern\n- `sessionStatus` — `'active'` | `'pending'`, for detecting incomplete session tasks\n- `userId`, `orgId`, `orgSlug`, `has()`, `protect()` — unchanged\n\n> **Core 2 ONLY (skip if current SDK):** `isAuthenticated` and `sessionStatus` are not available. Check `!!userId` instead.\n\n## Minimal Pattern\n\n```tsx\n// Server Component\nimport { auth } from '@clerk/nextjs/server'\nimport { Suspense } from 'react'\n\nasync function Greeting() {\n const { isAuthenticated, userId } = await auth() // MUST await!\n if (!isAuthenticated) return <p>Not signed in</p>\n return <p>Hello {userId}</p>\n}\n\nexport default function Page() {\n return (\n <Suspense>\n <Greeting />\n </Suspense>\n )\n}\n```\n\n`auth()` reads the request, so it runs in an async child inside `<Suspense>`, not in the page itself. See [Cache Components](#cache-components).\n\n> **Core 2 ONLY (skip if current SDK):** `isAuthenticated` is not available. Use `if (!userId)` instead.\n\n### Conditional Rendering with `<Show>`\n\n`<Show>` renders its children based on auth state. It covers both authentication checks and authorization (feature, plan, role, permission) in one component. In a Server Component, it reads the session from the request on the server, so wrap it in `<Suspense>`. In a Client Component, it reads client-side auth state and needs no boundary.\n\n**Authentication check:**\n\n```tsx\nimport { Show } from '@clerk/nextjs'\nimport { Suspense } from 'react'\n\n<Suspense>\n <Show when=\"signed-in\" fallback={<p>Please sign in</p>}>\n <Dashboard />\n </Show>\n</Suspense>\n```\n\n**Authorization checks (B2B)** (wrap in `<Suspense>` the same way when rendered in a Server Component):\n\n```tsx\n// Feature-based (preferred — features can move between plans without redeploy)\n<Show when={{ feature: 'analytics' }} fallback={<UpgradePrompt />}>\n <AnalyticsDashboard />\n</Show>\n\n// Permission-based (preferred over role-based for granular access)\n<Show when={{ permission: 'org:invoices:create' }}>\n <NewInvoiceButton />\n</Show>\n\n// Plan-based (tier-level gating)\n<Show when={{ plan: 'pro' }}>\n <ProFeatures />\n</Show>\n\n// Role-based (use sparingly — prefer permission)\n<Show when={{ role: 'org:admin' }}>\n <AdminPanel />\n</Show>\n```\n\n**Callback for complex logic:**\n\n```tsx\n<Show when={(has) => has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })}>\n <BillingActions />\n</Show>\n```\n\n> **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.\n\n## Cache Components\n\nNew Next.js apps set `cacheComponents: true` in `next.config` (the `create-next-app` default since Next.js 16.4). With it, `next build` fails on request data read outside a `<Suspense>` boundary: `Next.js encountered uncached or runtime data during prerendering`. See [Cache Components](https://clerk.com/docs/guides/development/rendering-modes#cache-components) in Clerk's rendering modes guide.\n\n- Wrap `<Show>` rendered in a Server Component, and any Server Component that calls `auth()` or `currentUser()`, in `<Suspense>`, as close to the auth-dependent UI as possible. Keep `<ClerkProvider>` and static layout outside the boundary so Next.js can prerender them.\n- In a layout header, put one `<Suspense>` inside `<header>` around both the `signed-out` and `signed-in` `<Show>` blocks.\n- A page that calls `auth()` or `currentUser()` to render data: move the read into an async child inside `<Suspense>` and keep the default-export page synchronous.\n- A page that gates its whole render with `auth.protect()`, `redirectToSignIn()`, or `redirect()`: leave the page as is and add a `loading.tsx` next to it. See [Read auth data inside Suspense](https://clerk.com/docs/guides/development/rendering-modes#read-auth-data-inside-suspense).\n- Wrap catch-all `<SignIn />`, `<SignUp />`, `<UserProfile />`, and `<OrganizationProfile />` pages in `<Suspense>`. They read the URL with `usePathname()`, even in a Client Component. See [Prebuilt components on catch-all routes](https://clerk.com/docs/guides/development/rendering-modes#prebuilt-components-on-catch-all-routes).\n- Never call `auth()` inside `'use cache'`. Read the ID outside and pass it in. See `references/caching-auth.md` and [Cache data per user with `'use cache'`](https://clerk.com/docs/guides/development/rendering-modes#cache-data-per-user-with-use-cache).\n\nUse `next build --debug-prerender` to list every failing route. A plain `next build` stops at the first.\n\n## Common Pitfalls\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `undefined` userId in Server Component | Missing `await` | `await auth()` not `auth()` |\n| Auth not working on API routes | Missing matcher | Add `'/(api|trpc)(.*)'` to `proxy.ts` (Next.js <=15: `middleware.ts`) |\n| `next build` fails with \"uncached or runtime data during prerendering\" | `auth()`, `currentUser()`, or server `<Show>` outside `<Suspense>` | Wrap the auth-dependent UI in `<Suspense>` (see [Cache Components](#cache-components)) |\n| Cache returns wrong user's data | `userId` not part of the cache key | Pass `userId` into the `'use cache'` function as an argument |\n| Mutations bypass auth | Unprotected Server Action | Check `auth()` at start of action |\n| Protected page returns 200, not 307, while signed out | The redirect happened after streaming started (e.g. under a `loading.tsx`) | Confirm the body redirects to sign-in and has no protected data. Keep the check in the page |\n| Wrong HTTP error code | Confused 401/403 | 401 = not signed in, 403 = no permission |\n\n## Session Tokens & Custom JWTs\n\n### getToken() for external APIs\n\nPass a custom JWT to third-party services (Hasura, Supabase, etc.) using JWT templates defined in the Clerk dashboard.\n\n**Server-side (Server Component or Route Handler)**:\n\n```tsx\nimport { auth } from '@clerk/nextjs/server'\n\nexport async function ExternalData() {\n const { getToken } = await auth()\n const token = await getToken({ template: 'hasura' })\n if (!token) return <p>Not authenticated</p>\n\n const res = await fetch('https://api.example.com/graphql', {\n headers: { Authorization: `Bearer ${token}` },\n })\n const data = await res.json()\n return <pre>{JSON.stringify(data)}</pre>\n}\n```\n\nRender `<ExternalData />` inside `<Suspense>`.\n\n**Client-side (Client Component)**:\n\n```typescript\n'use client'\nimport { useAuth } from '@clerk/nextjs'\n\nexport function DataFetcher() {\n const { getToken } = useAuth()\n\n async function fetchData() {\n const token = await getToken({ template: 'supabase' })\n if (!token) return\n\n const res = await fetch('https://api.example.com/data', {\n headers: { Authorization: `Bearer ${token}` },\n })\n return res.json()\n }\n\n return <button onClick={fetchData}>Fetch</button>\n}\n```\n\n`getToken()` returns `null` when the user is not authenticated — always null-check before use.\n\n### useSession() for session data\n\nAccess session metadata in client components:\n\n```typescript\n'use client'\nimport { useSession } from '@clerk/nextjs'\n\nexport function SessionInfo() {\n const { session } = useSession()\n if (!session) return null\n\n return (\n <p>\n Session {session.id} — last active: {session.lastActiveAt.toISOString()}\n </p>\n )\n}\n```\n\n### Manual JWT verification (no Clerk middleware)\n\nFor standalone API servers that receive Clerk session tokens from the `Authorization` header or the `__session` cookie (same-origin).\n\n**Using `@clerk/backend` `verifyToken`** (recommended):\n\n```typescript\nimport { verifyToken } from '@clerk/backend'\n\nconst token = req.headers.authorization?.replace('Bearer ', '')\nif (!token) return res.status(401).json({ error: 'No token' })\n\ntry {\n const claims = await verifyToken(token, {\n jwtKey: process.env.CLERK_JWT_KEY,\n })\n // claims.sub = userId\n} catch {\n return res.status(401).json({ error: 'Invalid token' })\n}\n```\n\n**Using `jsonwebtoken`** (when you can't use `@clerk/backend`):\n\n```typescript\nimport jwt from 'jsonwebtoken'\n\nconst publicKey = process.env.CLERK_PEM_PUBLIC_KEY!.replace(/\\\\n/g, '\\n')\nconst token = req.headers.authorization?.replace('Bearer ', '')\nif (!token) return res.status(401).json({ error: 'No token' })\n\ntry {\n const claims = jwt.verify(token, publicKey, { algorithms: ['RS256'] }) as jwt.JwtPayload\n // Manually check exp and nbf (jsonwebtoken does this automatically, but verify azp if needed)\n // claims.sub = userId\n} catch {\n return res.status(401).json({ error: 'Invalid or expired token' })\n}\n```\n\nToken sources:\n- **Same-origin requests**: `__session` cookie (Clerk sets this automatically)\n- **Cross-origin / mobile / API-to-API**: `Authorization: Bearer <token>` header\n\n> **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.\n\n## See Also\n\n- `clerk-setup` - Initial Clerk install\n- `clerk-orgs` - B2B patterns (active org, role/permission gating)\n- `clerk-billing` - Plan and feature entitlements with `has()`\n- `clerk-webhooks` - Sync user/org events to your database\n- `clerk-custom-ui` - Theming and customization for built-in components\n\n## Docs\n\n[Next.js SDK](https://clerk.com/docs/reference/nextjs/overview)\n"
}SHA-256 of public snapshot: 34ddfd6776e47561eab99d10ee8e790ecee62b7cafda5273f0ae21827ecc2af7