clerk-nextjs-patterns について
Clerk Next.js SDK を使う開発者向けの認証・認可パターン集です。サーバーとクライアントの API、ルート保護、トークン処理を扱います。
主な機能
- 01サーバーとクライアントの認証 API の使い分け
- 02各リソースでのルート保護
- 03Server Actions の保護
- 04API の 401 と 403 の処理
- 05外部 API 向けカスタム JWT の利用
- 06ユーザー間のキャッシュ混同の防止
活用シーン
- 01Server Component でのサインイン状態確認
- 02Route Handler と Server Function の保護
- 03ロールや権限による組織機能の制限
- 04外部 API への Clerk JWT の受け渡し
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ midudev/ autoskills --skill clerk-nextjs-patternsインストール後、Claude Code で/clerk-nextjs-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/api-routes.mdcaching-auth.mdmiddleware-strategies.mdserver-actions.mdserver-vs-client.mdtemplates/nextjs-basic-auth/package.jsonproxy.tstsconfig.jsontemplates/nextjs-basic-auth/app/layout.tsxpage.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/nextjs7 で非推奨となり、次のメジャーバージョンで削除される予定です。追加しないでください。プロジェクトですでに使用している場合は、そのまま動作する状態にして、ユーザーに移行ガイドを案内してください。
必要な情報は?
| タスク | 参照先 |
|---|---|
サーバーとクライアントの認証(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 |
参照資料
| 参照先 | 説明 |
|---|---|
references/server-vs-client.md | await auth() とフックの比較 |
references/middleware-strategies.md | 各リソースでの auth.protect()、proxy.ts で何も追加しない clerkMiddleware()(Next.js <=15 の場合:middleware.ts) |
references/server-actions.md | ミューテーションの保護 |
references/api-routes.md | 401 と 403 |
references/caching-auth.md | ユーザー単位のキャッシュ |
基本的な考え方
サーバーとクライアントでは、認証 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'
export default async function Page() {
const { isAuthenticated, userId } = await auth() // MUST await!
if (!isAuthenticated) return <p>Not signed in</p>
return <p>Hello {userId}</p>
}
Core 2 ONLY (skip if current SDK):
isAuthenticatedは利用できません。代わりにif (!userId)を使用してください。
<Show> による条件付きレンダリング
認証状態に基づいてクライアント側で条件付きレンダリングを行う場合に使用します。<Show> を使うと、認証チェックと認可(機能、プラン、ロール、権限)を1つのコンポーネントで行えます。
認証チェック:
import { Show } from '@clerk/nextjs'
<Show when="signed-in" fallback={<p>Please sign in</p>}>
<Dashboard />
</Show>
認可チェック(B2B):
// 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を参照してください。
よくある問題
| 症状 | 原因 | 修正方法 |
|---|---|---|
Server Component で userId が undefined | await の不足 | auth() ではなく await auth() を使用する |
| API ルートで認証が機能しない | matcher の不足 | proxy.ts(Next.js <=15 の場合:middleware.ts)に `'/(api |
| キャッシュから別のユーザーのデータが返る | キーに userId が含まれていない | unstable_cache のキーに userId を含める |
| ミューテーションで認証が回避される | Server Action が保護されていない | アクションの冒頭で auth() を確認する |
| サインアウト中に保護されたページが 307 ではなく 200 を返す | ストリーミング開始後(例:loading.tsx の配下)にリダイレクトが発生した | 本文がサインイン画面にリダイレクトされ、保護されたデータを含まないことを確認する。ページ内のチェックはそのままにする |
| HTTP エラーコードが正しくない | 401 と 403 の混同 | 401 = サインインしていない、403 = 権限がない |
セッショントークンとカスタム JWT
外部 API 用の getToken()
Clerk ダッシュボードで定義した JWT テンプレートを使用して、カスタム JWT をサードパーティサービス(Hasura、Supabase など)に渡します。
サーバー側(サーバーコンポーネントまたはルートハンドラー):
import { auth } from '@clerk/nextjs/server'
export default async function Page() {
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>
}
クライアント側(クライアントコンポーネント):
'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' })
}
トークンの送信元:
- 同一オリジンのリクエスト:
__sessionCookie(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- 組み込みコンポーネントのテーマ設定とカスタマイズ