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

FunctionSignatureDescription
useAuthReady()() => Promise<void>Returns the auth-ready Promise. SSR-side resolves immediately; client-side resolves after auth.client.ts completes
_resolveAuthReady()() => voidFramework-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.

Caveats

  • After await, you must explicitly check isLoggedIn (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 reading userStore.info yields null and a runtime crash.
  • Canonical form: await useAuthReady() → if (!userStore.isLoggedIn) return useLoginCheck() → then read user data.
  • Application code rarely needs to call it — request() already awaits useAuthReady() 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.