关于 clerk-nextjs-patterns
为使用 Clerk Next.js SDK 的开发者提供认证与授权实现模式,涵盖服务端和客户端 API、路由保护及令牌处理。
主要功能
- 01区分服务端与客户端认证 API
- 02逐页及处理程序保护路由
- 03保护 Server Actions
- 04处理 API 的 401 与 403
- 05使用自定义 JWT 访问外部 API
- 06避免用户数据缓存串用
适用场景
- 01在 Server Component 中检查登录状态
- 02保护 Route 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 模式
版本:请查看
package.json中的 SDK 版本,版本对照表见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 Actions | 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 Components 使用服务端导入,Client Components 使用钩子。
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> 可在同一个组件中处理身份验证检查和授权(功能、方案、角色、权限)。
身份验证检查:
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>。授权(角色 / 权限)请使用<Protect>,并采用相同的属性名称(role、permission、condition)。基于功能和方案的变体需要 Core 3。完整迁移对照表请参阅clerk-custom-ui技能中的core-3/show-component.md。
常见问题
| 现象 | 原因 | 解决方法 |
|---|---|---|
Server Component 中的 userId 为 undefined | 缺少 await | 使用 await auth(),而不是 auth() |
| API 路由中的身份验证不起作用 | 缺少 matcher | 在 proxy.ts(Next.js <=15:middleware.ts)中添加 `'/(api |
| 缓存返回了其他用户的数据 | 键中缺少 userId | 将 userId 加入 unstable_cache 键 |
| 变更操作绕过身份验证 | Server Action 未受保护 | 在操作开始时检查 auth() |
| 用户退出登录时,受保护页面返回 200 而非 307 | 重定向发生在流式传输开始之后(例如位于 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 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>
}
客户端(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' })
}
令牌来源:
- 同源请求:
__sessionCookie(Clerk 会自动设置) - 跨域请求 / 移动端 / API 到 API:
Authorization: Bearer <token>标头
重要:务必检查
exp和nbf声明。@clerk/backend中的verifyToken会自动处理这些检查;使用原生jsonwebtoken时,请设置ignoreExpiration: false(默认值),并确保clockTolerance尽可能小。
另请参阅
clerk-setup- 初始 Clerk 安装clerk-orgs- B2B 模式(当前组织、角色 / 权限控制)clerk-billing- 使用has()检查套餐和功能权益clerk-webhooks- 将用户 / 组织事件同步到数据库clerk-custom-ui- 内置组件的主题设置与自定义