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),確保請求不會在身分建立前發出。只有「登入就緒後要跑自己的邏輯」才需要顯式呼叫。