mcpField
C
clerk-nextjs-patterns

clerk-nextjs-patterns

github.com/midudev开发7.0k59 次安装1 周前更新
代码仓库

关于 clerk-nextjs-patterns

为使用 Clerk Next.js SDK 的开发者提供认证与授权实现模式,涵盖服务端和客户端 API、路由保护及令牌处理。

主要功能

  1. 01区分服务端与客户端认证 API
  2. 02逐页及处理程序保护路由
  3. 03保护 Server Actions
  4. 04处理 API 的 401 与 403
  5. 05使用自定义 JWT 访问外部 API
  6. 06避免用户数据缓存串用

适用场景

  1. 01在 Server Component 中检查登录状态
  2. 02保护 Route Handler 与 Server Function
  3. 03按角色或权限限制组织功能
  4. 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.json
references/
api-routes.md
caching-auth.md
middleware-strategies.md
server-actions.md
server-vs-client.md
templates/nextjs-basic-auth/
package.json
proxy.ts
tsconfig.json
templates/nextjs-basic-auth/app/
layout.tsx
page.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/nextjs 7 中已弃用,并将在下一个主版本中移除。请勿添加。如果项目已在使用,请保留其正常运行,并引导用户查看迁移指南。

你需要什么?

任务参考
服务端与客户端身份验证(auth() 与钩子)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

参考资料

参考说明
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按用户范围缓存

心智模型

服务端与客户端使用不同的身份验证 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/403401 = 未登录,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' })
}

令牌来源:

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

文档

Next.js SDK

评论

支持 Markdown