Why We Chose Better Auth Over NextAuth and Clerk

Authentication is the first thing every SaaS builds and the easiest to get wrong. Here's why our web templates standardize on Better Auth.

SoarStarter Team

Authentication is the first system every product builds and the one you least want to rewrite later. When we designed the web side of SoarStarter, the auth library was the load-bearing decision — everything else (payments, the dashboard, admin tooling) hangs off the session. We evaluated the obvious options and shipped all three web templates (Next.js, Nuxt, TanStack Start) on Better Auth. Here's the reasoning.

The three real contenders

For a self-hosted TypeScript SaaS in 2026, the field is roughly:

  • NextAuth / Auth.js — the long-time default in the Next.js world.
  • Clerk — a hosted auth service with polished pre-built UI.
  • Better Auth — a newer, framework-agnostic, fully self-hosted library.

All three can sign a user in. The differences show up in ownership, type safety, and how far they stretch across frameworks.

Reason 1: it's the same library on all three frameworks

This one is specific to us, but it drove the decision. We maintain the identical product on Next.js, Nuxt, and TanStack Start. Better Auth has a framework-agnostic core with thin framework adapters, so the auth concepts — the config object, the session shape, the plugin system — are identical across all three codebases. Our Next config and our Nuxt config read almost line-for-line the same:

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg", schema: { ...authSchema } }),
  emailAndPassword: { enabled: true, requireEmailVerification: true },
  socialProviders: { github: {...}, google: {...} },
  account: { accountLinking: { enabled: true } },
  plugins: [admin()],
});

NextAuth is, by name and by design, oriented around Next.js. Porting that mental model to a Vue/Nitro app or to TanStack Start would mean maintaining two genuinely different auth setups. With Better Auth, TanStack Start just adds one adapter plugin (tanstackStartCookies()) and otherwise looks the same.

Reason 2: you own the whole thing

Better Auth is a library, not a service. The user table lives in your PostgreSQL database via the Drizzle adapter; sessions are yours; there's no external dashboard and no per-user pricing meter running in the background.

Clerk is excellent at what it does, but it's a hosted dependency. You're renting your user identity, your pricing scales with monthly active users, and your login flow makes a round trip to someone else's infrastructure. For a template whose entire promise is "you own this code and ship it anywhere," a hosted auth provider would undercut the pitch. When a SoarStarter buyer deploys, their auth has zero external dependencies beyond the email sender.

Reason 3: TypeScript-first, with a plugin system that earns its keep

Better Auth is written for a TypeScript codebase — the session type flows through your app without hand-written declarations. But the part that actually sold us is the plugin model. Our templates need more than login: they need an admin role, email verification, and password reset. The admin() plugin gives us role management and the "act as admin" surface with one line, wired into the same session. Email verification and reset password are config options that hand us a URL; we render it with React Email and send it through Resend. No bolt-on packages fighting each other.

emailVerification: {
  sendOnSignUp: true,
  sendVerificationEmail: async ({ user, url }) => {
    await sendEmail({ to: user.email, subject: "Verify your email", react: VerifyEmail({ url, name: user.name }) });
  },
},

The honest caveat: we don't use it everywhere

We're not claiming Better Auth is the universal answer, and it's worth being clear about where we didn't choose it. Our mobile and desktop templates (SwiftUI, Kotlin, Expo, Electron, Tauri) use Supabase Auth instead — not because Better Auth is worse, but because on those platforms we also want a hosted Postgres, storage, and native client SDKs in one package, and Supabase bundles auth into that. Different platform, different constraint, different right answer. (We wrote a separate post on the native-app auth mechanics.)

That's the actual takeaway: pick auth based on where your app runs and what you're willing to own. For self-hosted web apps that value type safety and portability, Better Auth was the clear fit — enough that we bet three templates on it. If you want to see the full setup, the authentication docs walk through the exact config, schema, and email wiring.