Skip to content

auth · side by side

Better Auth vs Clerk for a Next.js app

Both fill the auth slot, so a generated repo carries one or the other, never both. Every line below is read out of the two manifests.

Short answer

Pick Better Auth if

Teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path.

Pick Clerk if

Teams who want finished auth UI and Clerk's hosted user dashboard on day one, with organisations there when they need them. The trade: the user record lives in Clerk's database. With a database selected, a webhook keeps a copy in yours.

Side by side

Price, obligations, and the surface each one adds. No row is written by hand. This is manifest.yaml, rendered.

Better Auth compared with Clerk on pricing, fit, trade-offs, required companions, environment variables, dependencies, MCP servers and repo footprint.
From the manifestOption ABetter AuthOption BClerk
In one lineBetter Auth

Own your users table. Passwords, magic links, Google, GitHub and Microsoft, all in your database.

Clerk

Hosted auth with prebuilt sign-in UI in your design's colours and a user sync webhook.

PricingBetter Auth

Free and open source (MIT). You pay only for your own Postgres and the sign-in emails you send. Google, GitHub and Microsoft sign-in are free.

Clerk

Free Hobby plan up to 50,000 monthly retained users per app. Pro is $25/month, or $20/month billed yearly, then $0.02 per extra user. MFA needs Pro. Pro includes one enterprise SSO connection.

Best forBetter Auth

Teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path.

Clerk

Teams who want finished auth UI and Clerk's hosted user dashboard on day one, with organisations there when they need them. The trade: the user record lives in Clerk's database. With a database selected, a webhook keeps a copy in yours.

Trade-offsVerbatim from the manifestBetter Auth
  • You own the security surface. Nobody rotates your signing secret, patches your session logic or answers a pen-test questionnaire for you.
  • No hosted UI. Sign-in and sign-up screens are yours to build and style, which is why this battery ships real ones instead of a redirect.
  • Email deliverability is your problem. A reset or magic link that lands in spam is an outage for that person.
  • Each OAuth provider is an app you register and keep alive yourself: callback URLs per environment, and a Microsoft client secret that expires.
  • Enterprise features other vendors sell as a plan tier (SAML, SCIM, audit log) are plugins or your own code here.
  • Upgrades are yours to run. New Better Auth minors sometimes add columns, so regenerating the schema is part of every upgrade.
Clerk
  • Prebuilt <SignIn /> and <SignUp /> components cover email, OAuth and account management out of the box. MFA needs the Pro plan.
  • Users live in Clerk, not in your Postgres. Anything that joins users to your tables needs the webhook mirror this battery ships with a database. The mirror is eventually consistent: a signup can reach your app a beat before the webhook lands.
  • Roles live in publicMetadata by default. Convenient, but it is vendor state. Moving to a local roles table later is a migration, not a config change.
  • Every server render that needs auth runs behind clerkMiddleware. Forget the proxy matcher and auth() throws at runtime instead of failing the build.
  • Pricing counts monthly retained users: people who come back 24+ hours after signing up. A large free consumer product costs more here than self-hosted auth.
  • Lock-in is real but bounded. Clerk exports users, and with a database selected this battery already keeps a copy in your own tables, keyed on your own id.
Required companionsAdded for you, with a reasonBetter Auth
  • An ORM battery
  • A database battery
  • An email battery
Clerk

Nothing. It stands on its own.

Recommended alongsideSuggested, never added for youBetter Auth

Nothing suggested.

Clerk
  • A database battery
Env vars you will manageEvery one documented in docs/onboard.mdBetter Auth

10 variables · 2 required

  • BETTER_AUTH_SECRET
  • BETTER_AUTH_URL
  • GITHUB_CLIENT_ID
  • GITHUB_CLIENT_SECRET
  • GOOGLE_CLIENT_ID
  • GOOGLE_CLIENT_SECRET
  • MICROSOFT_CLIENT_ID
  • MICROSOFT_CLIENT_SECRET
  • MICROSOFT_TENANT_ID
  • VERCEL_URL
Clerk

4 variables · 2 required

  • CLERK_SECRET_KEY
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
  • NEXT_PUBLIC_CLERK_SIGN_IN_URL
  • NEXT_PUBLIC_CLERK_SIGN_UP_URL
Dependencies addedBetter Auth
  • better-auth ~1.7.5
  • server-only ^0.0.1
Clerk
  • @clerk/nextjs ^7.9.7
  • server-only ^0.0.1
MCP serversWritten into .mcp.jsonBetter Auth
  • better-auth: https://mcp.better-auth.com/mcp
Clerk

None. No extra agent tools from this one.

Footprint in your repoBetter Auth

62 files, plus 5 injections into shared stack files

Clerk

29 files, plus 8 injections into shared stack files

What changes in your repo

The paths each battery contributes, diffed. A path in the third list is written by both, so swapping rewrites that file rather than adding one.

Only with Better Auth (54)

  • scripts/2 files
    • auth/2 files
      • make-admin.ts
      • seed.ts
  • src/46 files
    • app/7 files
      • (better-auth)/6 files
        • banned/1 file
          • page.tsx
        • forgot-password/1 file
          • page.tsx
        • reset-password/1 file
          • page.tsx
        • sign-in/1 file
          • page.tsx
        • sign-up/1 file
          • page.tsx
        • layout.tsx
      • api/1 file
        • auth/1 file
          • [...all]/1 file
            • route.ts
    • components/20 files
      • auth/20 files
        • settings/6 files
          • connected-accounts-card.tsx
          • email-card.tsx
          • password-card.tsx
          • profile-card.tsx
          • reauthenticate-alert.tsx
          • sessions-card.tsx
        • auth-card.tsx
        • auth-setup-notice.tsx
        • check-inbox.tsx
        • focus-first-error.ts
        • forgot-password-form.tsx
        • header-actions.tsx
        • magic-link-form.tsx
        • oauth-buttons.tsx
        • password-input.tsx
        • provider-icons.tsx
        • reset-password-form.tsx
        • session-provider.tsx
        • sign-in-form.tsx
        • sign-up-form.tsx
    • lib/19 files
      • auth/19 files
        • action-limit.test.ts
        • action-limit.ts
        • actions.ts
        • auth.ts
        • client.ts
        • endpoint-guard.test.ts
        • endpoint-guard.ts
        • errors.ts
        • guards.test.ts
        • list-sessions.test.ts
        • list-sessions.ts
        • policy.ts
        • providers.test.ts
        • providers.ts
        • schemas.ts
        • secret.ts
        • setup.ts
        • user-agent.test.ts
        • user-agent.ts
  • tests/2 files
    • e2e/2 files
      • auth.setup.ts
      • outbox.ts
  • variants/4 files
    • orm-drizzle/2 files
      • src/2 files
        • lib/2 files
          • auth/2 files
            • adapter.ts
            • schema.ts
    • orm-prisma/2 files
      • src/2 files
        • lib/2 files
          • auth/2 files
            • adapter.ts
            • schema.ts

Only with Clerk (21)

  • src/13 files
    • app/3 files
      • (auth)/3 files
        • sign-in/1 file
          • [[...sign-in]]/1 file
            • page.tsx
        • sign-up/1 file
          • [[...sign-up]]/1 file
            • page.tsx
        • layout.tsx
    • components/3 files
      • auth/3 files
        • clerk-root.tsx
        • clerk-user-profile.tsx
        • header-auth-actions.tsx
    • lib/7 files
      • auth/7 files
        • appearance.ts
        • clerk-env.d.ts
        • origin.ts
        • proxy.ts
        • publishable-key.ts
        • redirect.test.ts
        • redirect.ts
  • variants/8 files
    • db-sync/5 files
      • scripts/1 file
        • clerk-webhook.ts
      • src/4 files
        • app/1 file
          • api/1 file
            • webhooks/1 file
              • clerk/1 file
                • route.ts
        • lib/3 files
          • auth/3 files
            • user-sync.test.ts
            • user-sync.ts
            • webhook-idempotency.ts
    • orm-drizzle/2 files
      • src/2 files
        • db/1 file
          • clerk-schema.ts
        • lib/1 file
          • auth/1 file
            • user-store.ts
    • orm-prisma/1 file
      • src/1 file
        • lib/1 file
          • auth/1 file
            • user-store.ts

Same path, different implementation (8)

  • src/4 files
    • components/2 files
      • auth/2 files
        • account-settings.tsx
        • sign-out-button.tsx
    • lib/2 files
      • auth/2 files
        • roles.ts
        • session.ts
  • tests/2 files
    • e2e/2 files
      • auth.spec.ts
      • auth.ts
  • variants/2 files
    • orm-drizzle/1 file
      • slots/1 file
        • db-schema.ts
    • orm-prisma/1 file
      • slots/1 file
        • prisma-models.prisma

Shared stack files Better Auth injects into

  • bare-route-groups
  • env-required
  • header-actions
  • proxy-handlers
  • verify-checks

Shared stack files Clerk injects into

  • bare-route-groups
  • env-required
  • header-actions
  • legal-processors
  • middleware-matchers
  • providers
  • proxy-handlers
  • verify-checks

Better Auth in your .env.local

.env.localbash13 lines
# required
BETTER_AUTH_SECRET=replace-me
BETTER_AUTH_URL=http://localhost:3000

# optional
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_TENANT_ID=
VERCEL_URL=

Clerk in your .env.local

.env.localbash7 lines
# required
CLERK_SECRET_KEY=sk_test_replace_me
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_replace_me

# optional
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up

What changes for your agents

Each battery ships rules, skills, subagents and hooks that an agent loads before it touches the code that battery owns. Picking one is also picking how your agents behave in src/lib/auth/**.

Better Auth

  • 2

    Skills

  • 2

    Rules

  • 10

    Solution docs

  • 1

    MCP servers

Rules (2)

The auth server boundary and sign-in methods

src/lib/auth/** · src/app/api/auth/** · src/app/(better-auth)/** · src/components/auth/**

Roles are decided on the server, every time

src/app/** · src/components/** · src/lib/auth/**

Skills (2)

/add-oauth-provider

Turn on Google, GitHub or Microsoft sign-in (env keys only), or add another OAuth provider to the list in src/lib/auth/providers.ts.

/protect-route

Put an authentication or role check on a page, a route handler, a server action or a whole route group, at the right layer, without a redirect loop.

Subagents and hooks

None of its own. The foundation agents and guard hooks still ship.

Clerk

  • 2

    Skills

  • 3

    Rules

  • 7

    Solution docs

Rules (3)

Authorise from the session, never from the client user object

src/app/** · src/components/** · src/lib/auth/**

Clerk's components wear the design's tokens and only render inside the provider

src/components/auth/** · src/lib/auth/appearance.ts · src/app/(auth)/** · src/components/site/header.tsx

The Clerk webhook verifies its signature before anything else

src/app/api/webhooks/clerk/** · src/lib/auth/user-sync.ts · src/lib/auth/user-store.ts · src/lib/auth/webhook-idempotency.ts

Skills (2)

/add-clerk-role

Add a role to the Clerk-backed role vocabulary, wire it into the session claim, gate a route with it, and assign it safely.

/sync-clerk-user

Extend, backfill or re-verify the Clerk user mirror: the users table and ClerkSyncStore that this battery already ships.

Subagents and hooks

None of its own. The foundation agents and guard hooks still ship.

What each one already knows

Solution docs land in docs/solutions/ in your repo and are published here, so you can read the failure modes before you commit.

Better Auth (10)

Clerk (7)

Which one to pick

From meta.bestFor and meta.tradeoffs. If a claim is not in the manifest, it is not on this page.

Pick Better Auth when

Teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path.

And accept that(6)
  • You own the security surface. Nobody rotates your signing secret, patches your session logic or answers a pen-test questionnaire for you.
  • No hosted UI. Sign-in and sign-up screens are yours to build and style, which is why this battery ships real ones instead of a redirect.
  • Email deliverability is your problem. A reset or magic link that lands in spam is an outage for that person.
  • Each OAuth provider is an app you register and keep alive yourself: callback URLs per environment, and a Microsoft client secret that expires.
  • Enterprise features other vendors sell as a plan tier (SAML, SCIM, audit log) are plugins or your own code here.
  • Upgrades are yours to run. New Better Auth minors sometimes add columns, so regenerating the schema is part of every upgrade.

Pick Clerk when

Teams who want finished auth UI and Clerk's hosted user dashboard on day one, with organisations there when they need them. The trade: the user record lives in Clerk's database. With a database selected, a webhook keeps a copy in yours.

And accept that(6)
  • Prebuilt <SignIn /> and <SignUp /> components cover email, OAuth and account management out of the box. MFA needs the Pro plan.
  • Users live in Clerk, not in your Postgres. Anything that joins users to your tables needs the webhook mirror this battery ships with a database. The mirror is eventually consistent: a signup can reach your app a beat before the webhook lands.
  • Roles live in publicMetadata by default. Convenient, but it is vendor state. Moving to a local roles table later is a migration, not a config change.
  • Every server render that needs auth runs behind clerkMiddleware. Forget the proxy matcher and auth() throws at runtime instead of failing the build.
  • Pricing counts monthly retained users: people who come back 24+ hours after signing up. A large free consumer product costs more here than self-hosted auth.
  • Lock-in is real but bounded. Clerk exports users, and with a database selected this battery already keeps a copy in your own tables, keyed on your own id.

Questions people actually ask

Should I choose Better Auth or Clerk?
Better Auth is best for teams who want the user table in their own database, joinable with their own data. No per-MAU bill and no third-party outage in the login path. Clerk is best for teams who want finished auth UI and Clerk's hosted user dashboard on day one, with organisations there when they need them. The trade: the user record lives in Clerk's database. With a database selected, a webhook keeps a copy in yours. Both fill the auth slot, so a generated repo carries one or the other, never both.
How much do Better Auth and Clerk cost?
Better Auth: Free and open source (MIT). You pay only for your own Postgres and the sign-in emails you send. Google, GitHub and Microsoft sign-in are free. Clerk: Free Hobby plan up to 50,000 monthly retained users per app. Pro is $25/month, or $20/month billed yearly, then $0.02 per extra user. MFA needs Pro. Pro includes one enterprise SSO connection.
What changes in my repo if I switch from Better Auth to Clerk?
Better Auth writes 62 files, 10 environment variables and 2 dependencies, and installs 2 path-scoped rules, 2 skills and 10 solution docs. Clerk writes 29 files, 4 environment variables and 2 dependencies, and installs 3 path-scoped rules, 2 skills and 7 solution docs.

Decide once, then build the repo that already knows the decision.

Either way you get that choice’s rules, skills and solution docs installed, plus the guard hooks, an onboarding doc for exactly these env vars, and the Compound Engineering loop. Free and MIT.