useAuthReady()
framework 把 device + auth 身份完全 client-only 建立(v3 设计代际,framework#44) —— auth.client.ts plugin 在 hydrate 后才调用 userStore.getDeviceId() + userStore.autoLogin()。这中间有个空窗期:hydrate 完到 auth.client 完成之间,业务请求可能拿不到 token / deviceId。
useAuthReady() 就是这个空窗期的"等待门"。源码:src/runtime/composables/useAuthReady.ts。
大多数业务不需要手动用
framework request() 函数已经内置 await useAuthReady():
// framework/src/runtime/utils/request.ts
export async function request<T>(url, options) {
if (import.meta.client) await useAuthReady() // ← 内置门控
const { $request } = useNuxtApp()
return $request(url, options)
}
业务代码:
<script setup lang="ts">
defineRouteRules({ isr: 3600 })
onMounted(async () => {
// 不需要手动 useAuthReady(),request() 内部自动等
const data = await request('/api/user/favorite/check')
})
</script>
何时需要显式 await useAuthReady()
业务 UI 需要"等登录完才显示用户态片段"时显式等:
<script setup lang="ts">
defineRouteRules({ ssr: false })
const userStore = useUserStore()
const isReady = ref(false)
onMounted(async () => {
await useAuthReady() // ← 显式等
isReady.value = true
})
</script>
<template>
<div v-if="isReady">
<UserAvatar :nickname="userStore.info?.nickname" />
<VipBadge :is-vip="userStore.isLoggedIn" />
</div>
<SkeletonLoader v-else />
</template>
如果不显式等,isReady 立刻为 true 但 userStore.info 还没就绪 → 模板渲染空值 → 几百毫秒后突然出现真实值 → 闪烁。
API
| 函数 | 签名 | 说明 |
|---|---|---|
useAuthReady() | () => Promise<void> | 等待 auth-ready Promise。SSR 端立即 resolve;client 端等到 auth.client.ts 完成 |
_resolveAuthReady() | () => void | framework 内部使用,applier 业务代码不应调用。auth.client.ts 完成 device + login 后调它解除门控 |
SSR 行为
SSR 期 framework 永不主动建立 device/auth 身份(v3 设计核心),所以 SSR 没什么可等的。useAuthReady() 在 SSR 上下文立即 resolve。
业务侧无需为 SSR 单独写分支 —— 两端行为统一。
与 request() 内部的协作机制
auth.client.ts 的 login 流程自身也调 request('/login/device') 这类接口。如果 framework 内部 login 流程也走带 gate 的 request(),会形成 deadlock(login 等 auth-ready,auth-ready 等 login 完成)。
framework 的解法:内部用 frameworkInternalRequest() 绕过 gate。源码:
// utils/request.ts
export function frameworkInternalRequest<T>(url, options): Promise<T> {
const { $request } = useNuxtApp()
return $request(url, options) // 无 useAuthReady await
}
userStore.loginByUsername / loginByQrcode / loginByDevice 内部都走 frameworkInternalRequest(绕过 auth-ready 门控建立身份)。业务普通请求不要直接用 frameworkInternalRequest,全部用公开 request()。
失败兜底
auth.client.ts 内部用 try / finally 包装:
try {
userStore.getDeviceId()
if (!userStore.token) await userStore.autoLogin()
} finally {
_resolveAuthReady() // ← 即便 login 失败也解锁
}
最差情况:login 失败 → _resolveAuthReady 仍执行 → 业务请求按匿名状态发出。好过死锁让用户白屏。
auth: false 场景
swiftConfig: { auth: false }(docs 站等无 backend 项目)时 auth.client.ts 跳过 device + login,但仍然调 _resolveAuthReady() —— 业务 request() 不会被卡住,按匿名请求正常发出。
相关
- 自动登录 + Token 重登链路 — 完整时序图 + 三种渲染模式对照 + 限制与取舍
userStore— 用户身份 + token + 设备 ID- auth ModuleOptions —
auth: false整站关闭 client auth init - 渲染模式快速指南 — v3 设计原理 + 业务画像
注意事项
await之后必须显式检查isLoggedIn(Rule UAR-001)—— 本函数只保证「登录流程已结束」(device 初始化 + 凭证尝试都跑过),不保证登录成功:网络故障、凭证过期、用户主动登出都会让流程结束但无 token。误以为 await 后就一定已登录、直接访问userStore.info会拿到null并运行时崩溃。- 标准写法:
await useAuthReady()→if (!userStore.isLoggedIn) return useLoginCheck()→ 再访问用户数据。 - 业务侧一般不用手动调 ——
request()在 client 端已自动await useAuthReady()(Rule REQ-001),确保请求不会在身份建立前发出。只有「登录就绪后要跑自己的逻辑」才需要显式调用。