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()() => voidframework 內部使用,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() 不會被卡住,按匿名請求正常發出。

詳見 auth ModuleOptions。

相關

注意事項

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