关于 clerk-nextjs-patterns
面向使用 Clerk 与 Next.js 的开发者,提供认证、路由保护、缓存和令牌处理模式。内容按常见任务组织,并说明当前 SDK 与 Core 2 的差异。
主要功能
- 01服务端与客户端认证用法
- 02页面、路由和操作保护
- 03Cache Components 兼容模式
- 04JWT 与会话令牌处理
- 05常见认证故障排查
适用场景
- 01区分 Server Component 与 Client Component 的认证 API
- 02为页面和 Server Action 添加访问控制
- 03处理启用 Cache Components 后的构建问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ clerk/ skills --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/next.config.tspackage.jsonproxy.tstsconfig.jsontemplates/nextjs-basic-auth/app/layout.tsxpage.tsx详细介绍
Next.js 模式
版本:请查看
package.json中的 SDK 版本 — 版本表请参阅clerkskill。Core 2 的差异会在文中通过> **仅限 Core 2(当前 SDK 可跳过):**提示标出。
基本设置请参阅 clerk-setup skill。
**路由保护:**在每个页面、Route Handler 和 Server Function 中调用
await auth.protect()。保持clerkMiddleware()原样。createRouteMatcher在@clerk/nextjs7 中已弃用,并将在下一个主版本中移除。不要添加它。如果项目已在使用,请保持其正常工作,并引导用户查看迁移指南。
你需要什么?
| 任务 | 参考 |
|---|---|
服务端与客户端身份验证(auth() 与 hooks) | 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 |
修复 Cache Components 下的 next build 错误 | Cache Components |
参考
| 参考 | 说明 |
|---|---|
references/server-vs-client.md | await auth() 与 hooks |
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 | 使用 'use cache' 按用户范围缓存 |
心智模型
服务端与客户端 = 不同的身份验证 API:
- 服务端:来自
@clerk/nextjs/server的await auth()(异步!) - 客户端:来自
@clerk/nextjs的useAuth()hook(同步)
切勿混用。Server Components 使用服务端导入,Client Components 使用 hooks。
auth() 的关键属性:
isAuthenticated— 布尔值,替代!!userId模式sessionStatus—'active'|'pending',用于检测未完成的会话任务userId、orgId、orgSlug、has()、protect()— 保持不变
仅限 Core 2(当前 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(当前 SDK 可跳过):
isAuthenticated不可用。请改用if (!userId)。
使用 <Show> 进行条件渲染
<Show> 根据身份验证状态渲染其子项。它通过一个组件同时处理身份验证检查和授权(功能、套餐、角色、权限)。在 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(当前 SDK 可跳过):
<Show>不存在。身份验证请使用<SignedIn>和<SignedOut>。授权(角色 / 权限)请使用<Protect>,并使用相同的 prop 名称(role、permission、condition)。基于功能和套餐的变体需要 Core 3。完整迁移对照表请参阅clerk-custom-uiskill 中的core-3/show-component.md。
缓存组件
新的 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 渲染模式指南中的缓存组件。
- 在服务器组件中渲染的
<Show>,以及任何调用auth()或currentUser()的服务器组件,都应放在<Suspense>中,并尽可能靠近依赖身份验证的 UI。将<ClerkProvider>和静态布局放在边界之外,以便 Next.js 进行预渲染。 - 在布局页眉中,在
<header>内放置一个<Suspense>,将signed-out和signed-in两个<Show>块都包在其中。 - 如果页面调用
auth()或currentUser()来渲染数据:将读取操作移到<Suspense>内的异步子组件中,并保持默认导出的页面为同步组件。 - 如果页面使用
auth.protect()、redirectToSignIn()或redirect()控制整个页面的渲染:保持页面不变,并在其旁边添加loading.tsx。请参阅在 Suspense 中读取身份验证数据。 - 将兜底路由中的
<SignIn />、<SignUp />、<UserProfile />和<OrganizationProfile />页面包在<Suspense>中。即使在客户端组件中,它们也会使用usePathname()读取 URL。请参阅兜底路由中的预构建组件。 - 切勿在
'use cache'内调用auth()。请在外部读取 ID,并将其传入。请参阅references/caching-auth.md和使用'use cache'按用户缓存数据。
使用 next build --debug-prerender 列出所有失败的路由。直接运行 next build 会在遇到第一个失败项时停止。
常见问题
| 症状 | 原因 | 修复方法 |
|---|---|---|
服务器组件中的 undefined userId | 缺少 await | 使用 await auth(),而不是 auth() |
| API 路由中的身份验证不起作用 | 缺少匹配器 | 将 `'/(api |
next build 因 "uncached or runtime data during prerendering" 而失败 | <Suspense> 外部的 auth()、currentUser() 或服务器端 <Show> | 将依赖身份验证的 UI 包在 <Suspense> 中(请参阅缓存组件) |
| 缓存返回了其他用户的数据 | userId 不在缓存键中 | 将 userId 作为参数传入 'use cache' 函数 |
| 变更操作绕过身份验证 | 未受保护的服务器操作 | 在操作开始时检查 auth() |
| 用户退出登录时,受保护页面返回 200 而非 307 | 重定向发生在流式传输开始后(例如,在 loading.tsx 下) | 确认响应正文会重定向到登录页,且不包含受保护数据。将检查保留在页面中 |
| HTTP 错误代码错误 | 混淆了 401 和 403 | 401 = 未登录,403 = 无权限 |
会话令牌和自定义 JWT
使用 getToken() 调用外部 API
使用 Clerk 控制面板中定义的 JWT 模板,将自定义 JWT 传递给第三方服务(Hasura、Supabase 等)。
服务端(服务器组件或路由处理程序):
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>
}
在 <Suspense> 中渲染 <ExternalData />。
客户端(客户端组件):
'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>标头
CRITICAL:务必检查
exp和nbf声明。@clerk/backend中的verifyToken会自动处理这些检查;使用原始jsonwebtoken时,请设置ignoreExpiration: false(默认值),并确保clockTolerance尽可能小。
另请参阅
clerk-setup- 初始 Clerk 安装clerk-orgs- B2B 模式(活动组织、角色 / 权限限制)clerk-billing- 使用has()管理套餐和功能权益clerk-webhooks- 将用户 / 组织事件同步到数据库clerk-custom-ui- 内置组件的主题和自定义