40 lines | 1.9 KB

Better Auth conventions

One server instance

  • Create a single export const auth = betterAuth({ … }) in lib/auth.ts. It's the source of truth for sessions, providers, and plugins.
  • The auth instance is server-only — never import it into client components or ship it to the browser.

Configuration & secrets

  • Read secrets from the environment: BETTER_AUTH_SECRET, BETTER_AUTH_URL. Never hardcode them.
  • Enable auth methods explicitly (emailAndPassword, socialProviders.*), and keep provider client IDs/secrets in env.

Database

  • Use a database adapter that matches your stack (drizzleAdapter, prismaAdapter, …).
  • Generate the auth schema with npx @better-auth/cli generate, then apply it through your own migration tool. Don't hand-edit the generated auth tables.

Mounting & client

  • Mount the handler once. In Next App Router: app/api/auth/[...all]/route.ts exporting toNextJsHandler(auth).
  • Build the client with createAuthClient() from better-auth/react. For each server-side plugin, add its client counterpart so types and calls line up.
  • nextCookies() must be the last plugin in the array so cookies are set from server actions.

Sessions

  • On the server, read the session with auth.api.getSession({ headers: await headers() }). Pass the request headers — don't parse cookies manually.
  • On the client, use the auth client's useSession() / getSession().

Extending

  • Add capabilities (twoFactor, passkey, organization, magicLink, admin, …) through official plugins instead of bespoke auth code.

Anti-patterns

  • ❌ Importing the server auth instance into client code.
  • ❌ Hardcoded secrets instead of BETTER_AUTH_SECRET / env.
  • ❌ Reading/parsing the session cookie by hand instead of auth.api.getSession.
  • nextCookies() placed anywhere but last.
  • ❌ Hand-writing auth tables instead of the CLI-generated schema.