【免费下载链接】autoskills
One command. Your entire AI skill stack. Installed.
本篇指南聚焦packages/autoskills/skills-registry/clerk-astro-patterns技能包中的 SSR Pages 参考文档,系统讲解如何在 Astro 服务端渲染(SSR)页面中接入 Clerk 认证。你将掌握基于Astro.locals.auth()的基础登录校验、组织(Org)级页面权限控制、拉取当前用户资料、获取 JWT 调用外部 API,以及 SSR 与静态预渲染之间认证行为的差异与避坑要点。
前置条件与运行环境
本文示例基于@clerk/astroSDK(Clerk Astro SDK v3+),要求 Astro 4.15+。完整的项目骨架可以直接参考技能包中的 astro-basic-auth 模板,其依赖为astro(^5.0.0)、@clerk/astro(^2.0.0)与@astrojs/node(^9.0.0)。
SSR 页面认证依赖两个前提:
- 项目开启服务端渲染:在
astro.config.mjs中设置output: 'server'(或混合模式hybrid),并接入clerk()集成:
// astro.config.mjs import { defineConfig } from 'astro/config' import node from '@astrojs/node' import clerk from '@clerk/astro' export default defineConfig({ integrations: [clerk()], adapter: node({ mode: 'standalone' }), output: 'server', })- 配置 Clerk 中间件:
Astro.locals.auth()由clerkMiddleware在请求链路中填充。最简配置是透传式(pass-through)写法,把认证对象挂载到locals上,再由每个页面自行决定跳转逻辑:
// src/middleware.ts import { clerkMiddleware } from '@clerk/astro/server' export const onRequest = clerkMiddleware()模板 src/middleware.ts 正是这种写法。更完整的中间件(含createRouteMatcher路由匹配与集中式重定向)可参考 middleware.md。
基础认证检查:保护 Dashboard 页面
最常见的 SSR 认证场景,是在页面 frontmatter 中读取当前用户 ID,未登录则重定向到登录页。以下代码来自原文档的核心示例:
--- // src/pages/dashboard.astro const { userId } = Astro.locals.auth() if (!userId) return Astro.redirect('/sign-in') const data = await fetchData(userId) --- <h1>Dashboard</h1> <pre>{JSON.stringify(data)}</pre>要点拆解:
Astro.locals.auth()是一个函数调用,必须带括号(auth()),返回一个包含userId等字段的认证对象;fetchData(userId)表明后续数据获取以userId为参数,天然具备"按用户隔离数据"的能力;Astro.redirect('/sign-in')在 frontmatter 中直接返回,Astro 会将其转换为 302 重定向响应。
技能包的 evals.json 中第 2 条评估用例也印证了这一范式:在 frontmatter 中调用Astro.locals.auth()解构出userId,当userId为空时执行return Astro.redirect('/sign-in'),且页面必须是 SSR 输出(output: 'server'或未标记为 prerender)。
组织级页面:基于 Org 的多级权限控制
对于 B2B 场景,页面往往同时依赖"是否登录"与"是否处于某个组织上下文"。原文档给出了三层校验模式:
--- const { userId, orgId, orgRole } = Astro.locals.auth() if (!userId) return Astro.redirect('/sign-in') if (!orgId) return Astro.redirect('/select-org') if (orgRole !== 'org:admin') return Astro.redirect('/dashboard') const settings = await fetchOrgSettings(orgId) --- <h1>Org Settings</h1>这套守卫逻辑逐层递进,每一层失败都有明确的去向:
| 校验层级 | 判定条件 | 失败去向 |
|---|---|---|
| 身份层 | !userId | 重定向/sign-in(未登录) |
| 组织层 | !orgId | 重定向/select-org(未选择组织) |
| 角色层 | orgRole !== 'org:admin' | 重定向/dashboard(权限不足) |
角色字符串采用 Clerk 的标准格式org:admin,其中org:前缀标记为组织级角色。技能包 evals.json 第 6 条用例要求同时处理"未认证"与"未选择组织"两个状态,与本例完全一致。
获取当前用户数据:clerkClient 服务端用法
Astro.locals.auth()只提供会话元信息(如userId),要获取完整的用户档案(头像、姓名、邮箱等),需要通过服务端的clerkClient发起请求:
--- import { clerkClient } from '@clerk/astro/server' const { userId } = Astro.locals.auth() if (!userId) return Astro.redirect('/sign-in') const client = clerkClient(Astro) const user = await client.users.getUser(userId) --- <img src={user.imageUrl} alt={user.fullName ?? ''} />三个值得注意的细节:
clerkClient从@clerk/astro/server导入——这是服务端专用入口,与客户端 hooks 的导入路径严格区分;clerkClient(Astro)接收 Astro 上下文作为参数,以便携带请求级上下文(含密钥解析);user.imageUrl、user.fullName等字段来自 Clerk 用户对象;user.fullName ?? ''使用空值合并运算符兜底,避免头像 alt 属性为空字符串时的可访问性问题。
同样的clerkClient模式也适用于 API 路由:在 API 路由中传入的是context而非Astro,并配合context.locals.auth()读取认证信息。
getToken:为外部 API 签发 JWT
当 SSR 页面需要代表用户调用第三方服务(如 Supabase、自建后端)时,使用getToken获取针对指定模板签发的 JWT:
--- const auth = Astro.locals.auth() if (!auth.userId) return Astro.redirect('/sign-in') const token = await auth.getToken({ template: 'supabase' }) const data = await fetchFromSupabase(token) ---执行流程:
- 先取整个
auth对象,检查auth.userId是否存在; - 调用
await auth.getToken({ template: 'supabase' }),其中template对应你在 Clerk Dashboard 中配置的 JWT 模板名称——模板决定了 token 中携带的自定义 claims; - 将返回的 token 作为
Authorization头(通常为Bearer前缀)转发给外部 API。
getToken是异步函数,必须 await;模板不存在时,Clerk 会抛出错误,建议在调用处做好 try/catch 兜底。
Auth 对象字段全解
Astro.locals.auth()返回的认证对象包含以下字段(原文档表格完整继承):
| Field | Type | Description |
|---|---|---|
userId | string \| null | Current user ID |
orgId | string \| null | Active org ID |
orgRole | string \| null | User's role in active org |
sessionId | string \| null | Current session ID |
has() | function | Check permissions |
getToken() | async function | Get JWT for external APIs |
补充说明:
- 可空性语义:所有 ID 字段都可能为
null——未登录时userId为null;未选择组织时orgId为null;即使登录但组织内无角色时orgRole亦可能为null。因此页面守卫必须显式判空,不能依赖 truthy 巧合; has()权限检查:适用于细粒度权限(如auth.has({ permission: 'org:items:delete' })),可返回布尔值直接驱动页面内容分支。同一能力在 API 路由 的DELETE处理器中也有示范(配合 403 状态码);- 中间件的
auth()与页面不同:在src/middleware.ts中,认证对象由处理器参数回调取得(auth().userId),且同样必须调用auth()而非直接访问属性——详见 middleware.md 的 CRITICAL 说明。
关键注意事项(CRITICAL)
原文档明确列出的三条红线,是 SSR 认证稳定运行的生命线:
Astro.locals.auth()必须带括号调用——它返回认证对象,locals.auth(不带括号)只会得到未调用的函数引用,解构会得到undefined;- 受保护页面绝不能声明
export const prerender = true——Clerk 中间件会跳过静态预渲染页面,Astro.locals.auth()在这些页面上拿不到任何认证数据; - 如需将单个页面排除在静态渲染之外,显式声明
export const prerender = false,让该页回到 SSR 路径。
这条规则与中间件文档中的说明互为印证:clerkMiddleware对export const prerender = true的页面不执行。技能包 SKILL.md 的"Common Pitfalls"表给出了完整的症状对照:
| Symptom | Cause | Fix |
|---|---|---|
Astro.locals.authis undefined | Missing middleware | AddclerkMiddlewaretosrc/middleware.ts |
| Auth works in dev but not production | output: 'static'globally | Setoutput: 'server'orhybridfor protected pages |
| Static page has no auth | Prerendered pages skip middleware | Useexport const prerender = falseor move to island |
| Island not reactive to sign-in | Missingclient:loaddirective | Addclient:loadto the island component |
SSR 与静态渲染的取舍:一张图看懂请求链路
理解 Astro 的双渲染模式是正确使用 SSR 认证的前提。技能包给出了清晰的请求模型:
Request → clerkMiddleware() → SSR page → Astro.locals.auth() ↓ Island (.client) → useAuth() hook- SSR 页面:中间件填充
Astro.locals.auth(),服务端完成认证与重定向,适合 dashboard、组织设置等需要受保护数据渲染的页面; - 静态预渲染页面(
export const prerender = true):中间件直接跳过,服务端拿不到认证信息,只能依靠客户端岛屿组件中的useAuth()等 hooks 做前端条件渲染; - 岛屿组件(React/Vue/Svelte 等
.client组件):使用@clerk/astro/react的useAuth、useUser、UserButton等 hooks,且必须附带client:load等client:*指令才会在浏览器端水合。
对于"大部分静态 + 少数受保护"的混合站点,官方推荐output: 'hybrid'配合export const prerender = false逐页控制。技能包 evals.json 第 5 条用例也明确要求:静态预渲染页面上不能依赖Astro.locals.auth(),应改用岛屿客户端认证或将该页转为 SSR。
完整可运行的最小模板
技能包自带的最小模板 astro-basic-auth 展示了 SSR 认证的最小闭环:
astro.config.mjs:integrations: [clerk()]+adapter: node({ mode: 'standalone' })+output: 'server';src/middleware.ts:export const onRequest = clerkMiddleware()透传挂载认证对象;src/pages/index.astro:通过SignedIn/SignedOut组件做登录状态的条件渲染(页面级组件来自@clerk/astro/components)。
在此基础上,将本文的Astro.locals.auth()守卫模式写入任意.astro页面 frontmatter,即可完成受保护页面的服务端认证。若页面还需展示客户端实时认证状态(如登录/登出按钮),可结合 island-components.md 的useAuth+client:load方案;若需要 React 组件深度集成,参考 astro-react.md 的@clerk/astro/react与$userStore用法。
最后提醒:Clerk 环境变量遵循 Astro 的PUBLIC_前缀约定(而非 Next.js 的NEXT_PUBLIC_),在.env中配置PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...与CLERK_SECRET_KEY=sk_...后即可在本地运行验证。
【免费下载链接】autoskills
One command. Your entire AI skill stack. Installed.
相关推荐
Clerk 与 Astro 集成实战:中间件、SSR 页面、Island 组件与 API 路由的完整认证指南
Clerk 与 Astro 集成实战:中间件、SSR 页面、Island 组件与 API 路由的完整认证指南 本指南以 autoskills 仓库中 clerk
在 Astro 项目中用 Clerk 驱动 React Islands:@clerk/astro/react 完整实战指南
在 Astro 项目中用 Clerk 驱动 React Islands:@clerk/astro/react 完整实战指南 导读 在 Astro 的"岛屿架构"
在 Astro 中使用 Clerk 中间件:@clerk/astro 路由保护与鉴权全指南(autoskills clerk-astro-patterns)
在 Astro 中使用 Clerk 中间件:@clerk/astro 路由保护与鉴权全指南(autoskills clerk astro patterns) 本
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考