Sessions
Handle sessions with @nuxtjs/better-auth using useUserSession().
- `useUserSession()` is auto-imported and returns: user, session, loggedIn, ready, signOut, fetchSession
- SSR: session is fetched server-side via cookies and hydrated — `ready` is `true` after hydration
- Client: use `useSignIn()`, `useSignUp()`, or `useAuthClient()` for Better Auth client methods
- 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 })`
- Custom auth action sync: `runWithSessionRefresh(() => $fetch('/api/auth/...'))`
Use this page when you want to understand how session state moves between SSR, hydration, client updates, and server-side access.
const {
user,
session,
loggedIn,
ready,
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:
userandsessionare set when a valid session cookie exists, andreadyistrue. - Server runtime client access:
useAuthClient()returnsnullduring 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).
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 network request. This enables Better Auth's session refresh behavior.
If you want to skip that network request when user and session are already hydrated from SSR, enable the option below:
export default defineNuxtConfig({
auth: {
session: {
skipHydratedSsrGetSession: true,
},
},
})
The hydrated state answers Better Auth's initial session lookup without another network request. Better Auth's session refresh manager remains active for later focus, polling, online, broadcast, and auth-action refreshes.
Better Fetch still runs its normal request and response hooks for the hydrated bootstrap response; only the upstream network call is skipped.
The bootstrap response uses the sanitized user and session state exposed by useUserSession(), so it omits the session token and any custom top-level fields from the raw get-session response. Better Auth fetches the full client response on the next refresh.
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:
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.
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.
Cookie vs Database Sessions
Database sessions (default): stored in DB, revocable, visible in admin JWE sessions (database-less): encrypted cookie, no server storage
Handling Loading State
<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:
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>
When a custom Better Auth endpoint creates or changes the current session, wrap the action so Nuxt refreshes session state after a successful response:
await runWithSessionRefresh(() =>
$fetch('/api/auth/sign-in/custom', { method: 'POST', body }),
)
Session Lifetime
Configure session duration in your auth config:
import { defineServerAuth } from '@nuxtjs/better-auth/config'
export default defineServerAuth({
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
cookieCache: {
enabled: true,
maxAge: 5 * 60, // 5 minutes
},
},
})