External Auth Backend
Use this guide when the Nuxt app is only the frontend and Better Auth runs somewhere else.
When your Better Auth server runs on a separate backend (e.g., standalone h3/Nitro project, Express, or any other server), use clientOnly mode.
When to Use
- Microservices architecture: Auth service is a separate deployment
- Shared auth: Multiple frontends share one auth backend
- Existing backend: You already have a Better Auth server running elsewhere
Configuration
1. Enable Client-Only Mode
export default defineNuxtConfig({
modules: ['@nuxtjs/better-auth'],
auth: {
clientOnly: true,
},
})
2. Point the Client to the External Server
Set the site URL to your external auth server:
NUXT_PUBLIC_SITE_URL="https://auth.example.com"
This value becomes the default Better Auth client base URL in client-only mode. You can instead set baseURL in the client config. An explicit client config value takes precedence over the site URL:
import { defineClientAuth } from '@nuxtjs/better-auth/config'
export default defineClientAuth({
baseURL: 'https://auth.example.com',
})
3. Configure Route Redirect Targets (Optional)
Control redirect paths directly in route auth rules:
export default defineNuxtConfig({
routeRules: {
'/app/**': { auth: { only: 'user', redirectTo: '/login' } },
'/login': { auth: { only: 'guest', redirectTo: '/app' } },
},
})
What Changes in Client-Only Mode
| Feature | Full Mode | Client-Only |
|---|---|---|
server/auth.config.ts | Required | Not needed |
/api/auth/** handlers | Auto-registered | Skipped |
| Auth secret configuration | Required | Not needed |
| Server middleware | Enabled | Skipped |
| Schema generation | Enabled | Skipped |
| Devtools | Enabled | Skipped |
| SSR session hydration | Server-side | Client-side only |
useUserSession() | Works | Works |
| Route protection | Works | Works (client-side) |
<BetterAuthState> | Works | Works |
Important Notes
- CORS is configured on your auth server to allow requests from your frontend (with
credentials: true) - Cookies use
SameSite=None; Securewhen the frontend and auth backend are cross-site (HTTPS required). Same-site subdomains can useSameSite=Lax. - Your auth server's
trustedOriginsincludes your frontend URL
An origin includes the scheme, host, and port. A site uses the scheme and registrable domain, so https://app.example.com and https://auth.example.com are cross-origin but same-site. CORS still applies between them. See MDN's site definition and cookie SameSite attributes.
NUXT_PUBLIC_SITE_URL is used as the auth client base URL unless defineClientAuth provides an explicit baseURL.
Use routeRules.auth.redirectTo (or page meta auth.redirectTo) to control frontend navigation paths.serverAuth(), getUserSession() and requireUserSession() are not available in client-only mode since there's no local auth server.SSR Considerations
In client-only mode, session data is fetched client-side only. This means:
- Server-rendered pages won't have access to session data during SSR
- Pages will initially render as "unauthenticated" and hydrate with session data on the client
- Use
<BetterAuthState>component to handle loading states gracefully
<template>
<BetterAuthState>
<template #default="{ user }">
<div v-if="user">Welcome, {{ user.name }}</div>
<div v-else>Please log in</div>
</template>
<template #placeholder>
<div>Loading...</div>
</template>
</BetterAuthState>
</template>
If you need SSR session hydration, consider using full mode with a local auth server instead.
Example Architecture
┌─────────────────┐ ┌─────────────────┐
│ Nuxt App │────▶│ Auth Server │
│ (clientOnly) │ │ (Better Auth) │
│ │◀────│ │
└─────────────────┘ └────────┬────────┘
│
┌────────▼────────┐
│ Database │
└─────────────────┘
Related
- Configuration - Module options reference
- Database-Less Mode - JWE sessions without database