This library is in early development. Expect breaking changes.
Core Concepts

Sessions

Access reactive session state with SSR support using `useUserSession()`.
Prompt
Handle sessions with @onmax/nuxt-better-auth using useUserSession().

- `useUserSession()` is auto-imported and returns: user, session, loggedIn, ready, signIn, signUp, signOut, fetchSession
- SSR: session is fetched server-side via cookies and hydrated — `ready` is `true` after hydration
- Client: `.client` is `null` during SSR, available after hydration
- Split model: use `useUserSession()` in pages/components, use `requireUserSession(event)` in server handlers
- Configure session lifetime in `server/auth.config.ts` via `session.expiresIn` and `session.cookieCache`
- Use `<BetterAuthState>` or `ready` ref to avoid loading flashes
- Force refresh: `fetchSession({ force: true })`

Read more: https://better-auth.nuxt.dev/raw/core-concepts/sessions.md
Source: https://github.com/nuxt-modules/better-auth
pages/dashboard.vue
const {
  user,
  session,
  loggedIn,
  ready,
  signIn,
  signUp,
  signOut,
  fetchSession,
} = useUserSession()

SSR behavior

During server-side rendering (SSR), the module fetches the session using incoming request cookies and populates state before rendering. A client plugin then keeps the state in sync after hydration.

  • Server render: user and session are set when a valid session cookie exists, and ready is true.
  • Server runtime client access: useUserSession().client is null during SSR.
  • After hydration: State stays in sync with client-side updates.

Use a split model:

  • In pages and components, use useUserSession() state and SSR-safe fetch helpers (useAuthRequestFetch, useAuthAsyncData).
  • In server handlers and routes, use server utilities (serverAuth, getUserSession, requireUserSession).
server/api/profile.get.ts
export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event)
  return { id: user.id, email: user.email }
})

Optional: Skip Hydrated SSR Session Fetch

By default, SSR pages still bootstrap the client session with an initial /api/auth/get-session request. This enables Better Auth's session refresh behavior.

If you want to skip that request when user and session are already hydrated from SSR, enable the option below:

nuxt.config.ts
export default defineNuxtConfig({
  auth: {
    session: {
      skipHydratedSsrGetSession: true,
    },
  },
})
When enabled, the module skips Better Auth's session refresh manager on those SSR pages (focus, polling, broadcast). Session state will still sync after auth actions that trigger a session signal.

For prerendered or cached pages, the client plugin fetches the session after mount. Use ready or <BetterAuthState> to avoid flashes in those cases.

Auth Readiness vs Domain Data Readiness

ready and <BetterAuthState> guarantee only that auth hydration has finished. They do not guarantee that additional auth-bound domain data (billing state, role membership, entitlements) has loaded.

When rendering UI that depends on both auth and domain data, fetch that domain data SSR-first:

pages/app.vue
const { data: customerState } = await useFetch('/api/auth/customer/state')
const { data: customerById } = await useFetch('/api/auth/customer/123/state')

Endpoint payloads are inferred from your Better Auth config (core + plugins), including dynamic auth paths.

pages/app.vue
const { data: customerState, pending, error } = await useAuthAsyncData(
  'customer-state',
  requestFetch => requestFetch('/api/auth/customer/state'),
)

This prevents first-paint UI flips where unauthenticated defaults briefly appear before domain data resolves.

Database sessions (default): stored in DB, revocable, visible in admin JWE sessions (database-less): encrypted cookie, no server storage

Handling Loading State

pages/protected.vue
<script setup lang="ts">
const { user, loggedIn, ready } = useUserSession()
</script>

<template>
  <div v-if="!ready">Loading...</div>
  <div v-else-if="loggedIn">Welcome, {{ user?.name }}</div>
  <div v-else>Please log in</div>
</template>

Server-Side Session Access

For server handlers and API routes, use the server utilities:

server/api/profile.get.ts
export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event)
  return { id: user.id, email: user.email }
})

Session Refresh

Force refresh the session data:

<script setup lang="ts">
const { fetchSession, ready } = useUserSession()
</script>

<template>
  <button :disabled="!ready" @click="fetchSession({ force: true })">
    Refresh session
  </button>
</template>

Session Lifetime

Configure session duration in your auth config:

server/auth.config.ts
import { defineServerAuth } from '@onmax/nuxt-better-auth/config'

export default defineServerAuth({
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // 5 minutes
    },
  },
})