clerk-nextjs-patterns

作成者: clerk

Clerkを使用した認証、ミドルウェア、Server Actions、ユーザースコープキャッシングのための高度なNext.jsパターン。サーバーサイドのawait auth()とクライアントサイドのuseAuth()フックを区別します。これらを混在させることはよくある破壊的なミスです。ミドルウェア戦略(public-first vs protected-first)、APIルート保護、適切なHTTPステータスコード(401 vs 403)をカバーします。unstable_cacheを使用したユーザースコープキャッシングパターンと、Server Actionsを不正な変更から保護する方法を含みます。Core 2互換性を提供します...

npx skills add https://github.com/clerk/skills --skill clerk-nextjs-patterns

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.

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.

What Do You Need?

TaskReference
Server vs client auth (auth() vs hooks)references/server-vs-client.md
Protect routes (check in each page, handler, and Server Function)references/middleware-strategies.md
Protect Server Actionsreferences/server-actions.md
API route auth (401 vs 403)references/api-routes.md
Cache auth data (user-scoped caching)references/caching-auth.md
Fix next build errors under Cache ComponentsCache Components

References

ReferenceDescription
references/server-vs-client.mdawait auth() vs hooks
references/middleware-strategies.mdauth.protect() in each resource, bare clerkMiddleware() in proxy.ts (Next.js <=15: middleware.ts)
references/server-actions.mdProtect mutations
references/api-routes.md401 vs 403
references/caching-auth.mdUser-scoped caching with 'use cache'

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

// Server Component
import { auth } from '@clerk/nextjs/server'
import { Suspense } from 'react'

async function Greeting() {
  const { isAuthenticated, userId } = await auth() // MUST await!
  if (!isAuthenticated) return <p>Not signed in</p>
  return <p>Hello {userId}</p>
}

export default function Page() {
  return (
    <Suspense>
      <Greeting />
    </Suspense>
  )
}

auth() reads the request, so it runs in an async child inside <Suspense>, not in the page itself. See Cache Components.

Core 2 ONLY (skip if current SDK): isAuthenticated is not available. Use if (!userId) instead.

Conditional Rendering with <Show>

<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.

Authentication check:

import { Show } from '@clerk/nextjs'
import { Suspense } from 'react'

export function AuthenticatedDashboard() {
  return (
    <Suspense>
      <Show when="signed-in" fallback={<p>Please sign in</p>}>
        <Dashboard />
      </Show>
    </Suspense>
  )
}

Authorization checks (B2B) (wrap in <Suspense> the same way when rendered in a Server Component):

// Feature-based (preferred — features can move between plans without redeploy)
<Show when={{ feature: 'analytics' }} fallback={<UpgradePrompt />}>
  <AnalyticsDashboard />
</Show>

// Permission-based (preferred over role-based for granular access)
<Show when={{ permission: 'org:invoices:create' }}>
  <NewInvoiceButton />
</Show>

// Plan-based (tier-level gating)
<Show when={{ plan: 'pro' }}>
  <ProFeatures />
</Show>

// Role-based (use sparingly — prefer permission)
<Show when={{ role: 'org:admin' }}>
  <AdminPanel />
</Show>

Callback for complex logic:

<Show when={(has) => has({ role: 'org:admin' }) || has({ role: 'org:billing_manager' })}>
  <BillingActions />
</Show>

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.

Cache Components

New 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 in Clerk's rendering modes guide.

  • 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.
  • In a layout header, put one <Suspense> inside <header> around both the signed-out and signed-in <Show> blocks.
  • 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.
  • 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.
  • 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.
  • 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'.

Use next build --debug-prerender to list every failing route. A plain next build stops at the first.

Common Pitfalls

SymptomCauseFix
undefined userId in Server ComponentMissing awaitawait auth() not auth()
Auth not working on API routesMissing matcherAdd '/api(.*)' and '/trpc(.*)' to proxy.ts (Next.js <=15: middleware.ts)
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 returns wrong user's datauserId not part of the cache keyPass userId into the 'use cache' function as an argument
Mutations bypass authUnprotected Server ActionCheck auth() at start of action
Protected page returns 200, not 307, while signed outThe 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
Wrong HTTP error codeConfused 401/403401 = 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):

import { auth } from '@clerk/nextjs/server'

export async function ExternalData() {
  const { getToken } = await auth()
  const token = await getToken({ template: 'hasura' })
  if (!token) return <p>Not authenticated</p>

  const res = await fetch('https://api.example.com/graphql', {
    headers: { Authorization: `Bearer ${token}` },
  })
  const data = await res.json()
  return <pre>{JSON.stringify(data)}</pre>
}

Render <ExternalData /> inside <Suspense>.

Client-side (Client Component):

'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 <button onClick={fetchData}>Fetch</button>
}

getToken() returns null when the user is not authenticated — always null-check before use.

useSession() for session data

Access session metadata in client components:

'use client'
import { useSession } from '@clerk/nextjs'

export function SessionInfo() {
  const { session } = useSession()
  if (!session) return null

  return (
    <p>
      Session {session.id} — last active: {session.lastActiveAt.toISOString()}
    </p>
  )
}

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):

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):

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 <token> 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

clerkのその他のスキル

clerk-testing
clerk
Clerk認証フローのE2Eテストユーティリティ(PlaywrightおよびCypress対応)。PlaywrightとCypressの両フレームワークをサポートし、フレームワーク固有のセットアップパターン(PlaywrightではglobalSetup、Cypressではカスタムコマンド)を提供。clerkSetup()とsetupClerkTestingToken()ユーティリティを使用してテスト環境を初期化し、ボット検出をバイパス。認証済みセッションをテスト間で再利用するstorageState永続化機能を含み、テスト実行時間を短縮。テスト用APIキー(pk_test_*、...)が必要。
clerk-swift
clerk
ClerkKitを使用したネイティブSwift/iOS認証で、事前構築済みまたはカスタム認証フローに対応。プロジェクト要件に基づいて、事前構築済みのAuthViewコンポーネントまたは完全カスタムのネイティブフローの2つの実装モードをサポート。アプリ設定で有効なClerk公開鍵を直接配線する必要があり、デフォルトではplistや環境ファイルによる間接指定は使用しない。パッケージインストール後、機能の利用可能性(例:Apple Sign Inサポート)を判断するために、/v1/environment呼び出しが必須。
mosaic
clerk
Work on Mosaic UI: styling a component with slot recipes (`defineSlotRecipe` / `useRecipe` / slots / variants), or building a flow — authoring a state machine…
clerk-billing
clerk
サブスクリプション管理のためのClerk Billing - ClerkのPricingTableをレンダリング
clerk-expo-patterns
clerk
Expo / React NativeパターンとClerk — SecureStoreトークンキャッシュ、OAuth
changesets
clerk
Create or refresh a `.changeset/<slug>.md` for the current branch, or report that none is required. Triggers on "/changesets create", "add a changeset",…
clerk-astro-patterns
clerk
Clerkを使ったAstroパターン — ミドルウェア、SSRページ、アイランドコンポーネント
clerk
clerk
clerkバイナリは、ClerkのBackend APIおよびPlatform APIへの事前認証済みゲートウェイであり、プロジェクトレベルのツール(認証、リンク、環境変数の取得、インスタンス設定)も提供します。ユーザーがClerkリソースに関わる何かを尋ねた場合、手動でcurlを使う代わりに、まずclerkを使用してください。