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. 01区分 Server Component 与 Client Component 的认证 API
  2. 02为页面和 Server Action 添加访问控制
  3. 03处理启用 Cache 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 模式

版本:请查看 package.json 中的 SDK 版本 — 版本表请参阅 clerk skill。Core 2 的差异会在文中通过 > **仅限 Core 2(当前 SDK 可跳过):** 提示标出。

基本设置请参阅 clerk-setup skill。

**路由保护:**在每个页面、Route Handler 和 Server Function 中调用 await auth.protect()。保持 clerkMiddleware() 原样。createRouteMatcher 在 @clerk/nextjs 7 中已弃用,并将在下一个主版本中移除。不要添加它。如果项目已在使用,请保持其正常工作,并引导用户查看迁移指南。

你需要什么?

任务参考
服务端与客户端身份验证(auth() 与 hooks)references/server-vs-client.md
保护路由(在每个页面、处理程序和 Server Function 中检查)references/middleware-strategies.md
保护 Server Actionsreferences/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() 与 hooks
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() 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-ui skill 中的 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 和 403401 = 未登录,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' })
}

令牌来源:

  • 同源请求:__session Cookie(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 - 内置组件的主题和自定义

文档

Next.js SDK

评论

支持 Markdown