mcpField
C
clerk-nextjs-patterns

clerk-nextjs-patterns

github.com/clerk開発8954.0k 回インストール今日更新
リポジトリ

clerk-nextjs-patterns について

Clerk と Next.js を使う開発者向けに、認証、ルート保護、キャッシュ、トークン処理のパターンをまとめたスキルです。タスク別の資料で、現行 SDK と Core 2 の違いも説明します。

主な機能

  1. 01サーバーとクライアントの認証パターン
  2. 02ページ、ルート、アクションの保護
  3. 03Cache Components への対応
  4. 04JWT とセッショントークンの処理
  5. 05認証でよくある問題の診断

活用シーン

  1. 01Server Component と Client Component の認証 API を選ぶ
  2. 02ページや Server Action にアクセス制御を加える
  3. 03Cache Components 使用時のビルド問題に対処する

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/clerk-nextjs-patternsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
api-routes.md
caching-auth.md
middleware-strategies.md
server-actions.md
server-vs-client.md
templates/nextjs-basic-auth/
next.config.ts
package.json
proxy.ts
tsconfig.json
templates/nextjs-basic-auth/app/
layout.tsx
page.tsx

詳細

Next.js のパターン

バージョン: SDK のバージョンは package.json で確認してください。バージョン一覧は clerk スキルを参照してください。Core 2 との違いは、> **Core 2 ONLY (skip if current SDK):** の注記でインラインに示しています。

基本的なセットアップについては、clerk-setup スキルを参照してください。

ルート保護: 各ページ、Route Handler、Server Function で await auth.protect() を呼び出してください。clerkMiddleware() はそのままにしてください。createRouteMatcher は @clerk/nextjs 7 で非推奨となり、次のメジャーバージョンで削除されます。追加しないでください。プロジェクトですでに使用されている場合は、そのまま動作する状態にし、ユーザーに移行ガイドを案内してください。

必要な情報は?

タスク参照先
サーバーとクライアントの認証(auth() とフック)references/server-vs-client.md
ルートの保護(各ページ、ハンドラー、Server Function で確認)references/middleware-strategies.md
Server Action の保護references/server-actions.md
API ルートの認証(401 と 403)references/api-routes.md
認証データのキャッシュ(ユーザー単位のキャッシュ)references/caching-auth.md
Cache Components での next build エラーの修正Cache Components

参照先

参照先説明
references/server-vs-client.mdawait auth() とフックの比較
references/middleware-strategies.md各リソースでの auth.protect()、proxy.ts でそのまま使う clerkMiddleware()(Next.js <=15 の場合は middleware.ts)
references/server-actions.md更新処理の保護
references/api-routes.md401 と 403
references/caching-auth.md'use cache' によるユーザー単位のキャッシュ

基本的な考え方

サーバーとクライアントでは、認証 API が異なります。

  • サーバー: @clerk/nextjs/server の await auth()(非同期です!)
  • クライアント: @clerk/nextjs の useAuth() フック(同期です)

決して混在させないでください。Server Component ではサーバー用のインポートを使用し、Client Component ではフックを使用してください。

auth() の主なプロパティ:

  • isAuthenticated — ブール値。!!userId パターンの代わりに使用します
  • sessionStatus — 'active' | 'pending'。未完了のセッションタスクを検出します
  • userId、orgId、orgSlug、has()、protect() — 変更はありません

Core 2 ONLY (skip if current SDK): isAuthenticated と sessionStatus は使用できません。代わりに !!userId を確認してください。

最小パターン

// 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() はリクエストを読み取るため、ページ自体ではなく、<Suspense> 内の非同期子コンポーネントで実行します。Cache Componentsを参照してください。

Core 2 ONLY (skip if current SDK): isAuthenticated は使用できません。代わりに if (!userId) を使用してください。

<Show> による条件付きレンダリング

<Show> は認証状態に基づいて子要素をレンダリングします。認証チェックと認可(機能、プラン、ロール、権限)の両方を、1 つのコンポーネントで処理できます。Server Component では、サーバー上でリクエストからセッションを読み取るため、<Suspense> で囲んでください。Client Component では、クライアント側の認証状態を読み取るため、境界は不要です。

認証チェック:

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

<Suspense>
  <Show when="signed-in" fallback={<p>Please sign in</p>}>
    <Dashboard />
  </Show>
</Suspense>

認可チェック(B2B)(Server Component でレンダリングする場合も、同様に <Suspense> で囲んでください):

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

複雑なロジックのコールバック:

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

Core 2 ONLY (skip if current SDK): <Show> は存在しません。認証には <SignedIn> と <SignedOut> を使用してください。認可(ロール/権限)には、同じプロパティ名(role、permission、condition)を使って <Protect> を使用してください。機能およびプランに基づくバリエーションには Core 3 が必要です。完全な移行表については、clerk-custom-ui スキルの core-3/show-component.md を参照してください。

Cache Components

新しい Next.js アプリでは、next.config に cacheComponents: true が設定されます(Next.js 16.4 以降の create-next-app のデフォルト)。これを有効にすると、<Suspense> 境界の外でリクエストデータを読み取る場合、next build は失敗します。エラーは Next.js encountered uncached or runtime data during prerendering です。Clerk のレンダリングモードガイドにある Cache Components を参照してください。

  • Server Component 内でレンダリングされる <Show> と、auth() または currentUser() を呼び出す Server Component は、認証に依存する UI にできるだけ近い位置で <Suspense> で囲んでください。Next.js がプリレンダリングできるよう、<ClerkProvider> と静的なレイアウトは境界の外に置いてください。
  • レイアウトのヘッダーでは、<header> 内に <Suspense> を 1 つ配置し、その中で signed-out と signed-in の両方の <Show> ブロックを囲んでください。
  • データをレンダリングするために auth() または currentUser() を呼び出すページでは、読み取り処理を <Suspense> 内の非同期の子コンポーネントに移し、デフォルトエクスポートするページは同期処理のままにしてください。
  • ページ全体のレンダリングを auth.protect()、redirectToSignIn()、または redirect() で制御する場合は、ページはそのままにして、隣に loading.tsx を追加してください。Read auth data inside Suspense を参照してください。
  • キャッチオールルートの <SignIn />、<SignUp />、<UserProfile />、<OrganizationProfile /> ページは、<Suspense> で囲んでください。これらは Client Component 内でも usePathname() で URL を読み取ります。Prebuilt components on catch-all routes を参照してください。
  • 'use cache' の中で auth() を呼び出さないでください。ID は外で読み取り、渡してください。references/caching-auth.md と 'use cache' を使ってユーザーごとにデータをキャッシュする を参照してください。

失敗するルートをすべて一覧表示するには、next build --debug-prerender を使用してください。通常の next build は最初のエラーで停止します。

よくある落とし穴

症状原因修正方法
Server Component 内の userId が undefinedawait が不足しているauth() ではなく await auth() を使用する
API ルートで認証が機能しないmatcher が不足しているproxy.ts に `'/(api
next build が "uncached or runtime data during prerendering" で失敗する<Suspense> の外に auth()、currentUser()、またはサーバー側の <Show> がある認証に依存する UI を <Suspense> で囲む(Cache Components を参照)
キャッシュから別のユーザーのデータが返されるuserId がキャッシュキーに含まれていない'use cache' 関数に引数として userId を渡す
ミューテーションが認証を迂回するServer Action が保護されていないアクションの先頭で auth() を確認する
サインアウト中の保護されたページが 307 ではなく 200 を返すストリーミング開始後にリダイレクトされた(例:loading.tsx の下)本文がサインインページにリダイレクトし、保護されたデータを含まないことを確認する。チェックはページ内に残す
HTTP エラーコードが間違っている401 と 403 を混同している401 = サインインしていない、403 = 権限がない

セッショントークンとカスタム JWT

外部 API 用の getToken()

Clerk ダッシュボードで定義した JWT テンプレートを使って、カスタム JWT をサードパーティサービス(Hasura、Supabase など)に渡します。

サーバー側(Server Component または 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>
}

<ExternalData /> を <Suspense> 内でレンダリングしてください。

クライアント側(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() は null を返します。使用前に必ず null チェックを行ってください。

セッションデータ用の useSession()

クライアントコンポーネントでセッションのメタデータにアクセスします。

'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>
  )
}
手動での JWT 検証(Clerk ミドルウェアなし)

Authorization ヘッダーまたは __session Cookie(同一オリジン)から Clerk のセッショントークンを受け取るスタンドアロン API サーバー向けです。

@clerk/backend の verifyToken を使用する場合(推奨):

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' })
}

jsonwebtoken を使用する場合(@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' })
}

トークンの取得元:

  • 同一オリジンのリクエスト:__session Cookie(Clerk が自動的に設定します)
  • クロスオリジン/モバイル/API 間:Authorization: Bearer <token> ヘッダー

CRITICAL:exp と nbf のクレームは必ず確認してください。@clerk/backend の verifyToken はこの確認を自動的に処理しますが、生の jsonwebtoken を使用する場合は、ignoreExpiration: false(デフォルト)を設定し、clockTolerance を最小限にしてください。

関連項目

  • clerk-setup - Clerk の初期セットアップ
  • clerk-orgs - B2B パターン(アクティブな組織、ロール/権限によるアクセス制御)
  • clerk-billing - has() を使ったプランと機能の利用権限
  • clerk-webhooks - ユーザー/組織のイベントをデータベースに同期
  • clerk-custom-ui - 組み込みコンポーネントのテーマ設定とカスタマイズ

ドキュメント

Next.js SDK

コメント