useAuthReady()
The framework establishes device + auth identity entirely client-only (the v3 design generation, framework#44) — auth.client.ts calls userStore.getDeviceId() + userStore.autoLogin() only after hydrate. There's a window in between: between hydrate finishing and auth.client completing, business requests may not have token / deviceId yet.
useAuthReady() is the "gate" for that window. Source: src/runtime/composables/useAuthReady.ts.
Most business code doesn't need to use this directly
The framework's request() function has await useAuthReady() built in:
// framework/src/runtime/utils/request.ts
export async function request<T>(url, options) {
if (import.meta.client) await useAuthReady() // ← built-in gate
const { $request } = useNuxtApp()
return $request(url, options)
}
Business code:
<script setup lang="ts">
defineRouteRules({ isr: 3600 })
onMounted(async () => {
// No need for useAuthReady() manually — request() awaits internally
const data = await request('/api/user/favorite/check')
})
</script>
When you DO need explicit await useAuthReady()
When UI needs to "wait until login finishes before showing user-state fragments":
<script setup lang="ts">
defineRouteRules({ ssr: false })
const userStore = useUserStore()
const isReady = ref(false)
onMounted(async () => {
await useAuthReady() // ← explicit await
isReady.value = true
})
</script>
<template>
<div v-if="isReady">
<UserAvatar :nickname="userStore.info?.nickname" />
<VipBadge :is-vip="userStore.isLoggedIn" />
</div>
<SkeletonLoader v-else />
</template>
Without explicit waiting, isReady flips to true immediately while userStore.info is still empty — template renders nothing, then a few hundred ms later the real values appear → flash.
API
| Function | Signature | Description |
|---|---|---|
useAuthReady() | () => Promise<void> | Returns the auth-ready Promise. SSR-side resolves immediately; client-side resolves after auth.client.ts completes |
_resolveAuthReady() | () => void | Framework-internal — applier business code must not call this. auth.client.ts calls it after device + login establishment to release the gate |
SSR behavior
In SSR, the framework never establishes device/auth identity (the core of the v3 design), so nothing to wait for. useAuthReady() resolves immediately in SSR context.
No need for SSR-specific branches in business code — behavior is uniform across both runtimes.
How request() cooperates internally
auth.client.ts's own login flow calls request('/login/device') and similar endpoints. If the framework's internal login flow also went through the gated request(), you'd get deadlock (login awaits auth-ready, auth-ready awaits login).
The framework's solution: internal code uses frameworkInternalRequest(), which bypasses the gate. Source:
// utils/request.ts
export function frameworkInternalRequest<T>(url, options): Promise<T> {
const { $request } = useNuxtApp()
return $request(url, options) // no useAuthReady await
}
userStore.loginByUsername / loginByQrcode / loginByDevice internally use frameworkInternalRequest (bypasses the auth-ready gate to establish identity). Don't call frameworkInternalRequest directly for normal business requests — always use the public request().
Failure fallback
auth.client.ts wraps its work in try / finally:
try {
userStore.getDeviceId()
if (!userStore.token) await userStore.autoLogin()
} finally {
_resolveAuthReady() // ← unlocks even if login throws
}
Worst case: login fails → _resolveAuthReady still runs → business requests fire as anonymous. Better than deadlocking on a white screen.
The auth: false scenario
When swiftConfig: { auth: false } (docs site / no-backend projects), auth.client.ts skips device + login but still calls _resolveAuthReady() — business request() won't hang; it just fires as anonymous.
See auth ModuleOptions.
Related
- Auto-Login + Token Refresh Flow — full sequence diagram + behavior across rendering modes + trade-offs
userStore— user identity + token + device ID- auth ModuleOptions —
auth: falsedisables client auth init site-wide - Rendering Mode Quick Guide — v3 design rationale + business profile
Caveats
- After
await, you must explicitly checkisLoggedIn(Rule UAR-001) — this only guarantees the login flow finished (device init plus credential attempts all ran); it does not guarantee login succeeded. Network failure, expired credentials, or an explicit logout all end the flow without a token. Assuming "awaited means logged in" and readinguserStore.infoyieldsnulland a runtime crash. - Canonical form:
await useAuthReady()→if (!userStore.isLoggedIn) return useLoginCheck()→ then read user data. - Application code rarely needs to call it —
request()alreadyawaitsuseAuthReady()on the client (Rule REQ-001) so requests never fire before identity is established. Call it explicitly only when your own logic must run once login settles.