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.
| From the manifest | Option ABetter Auth | Option BClerk |
|---|---|---|
| In one line | Better 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. |
| Pricing | 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. |
| Best for | Better 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 manifest | Better Auth
| Clerk
|
| Required companionsAdded for you, with a reason | Better Auth
| Clerk Nothing. It stands on its own. |
| Recommended alongsideSuggested, never added for you | Better Auth Nothing suggested. | Clerk
|
| Env vars you will manageEvery one documented in docs/onboard.md | Better Auth 10 variables · 2 required
| Clerk 4 variables · 2 required
|
| Dependencies added | Better Auth
| Clerk
|
| MCP serversWritten into .mcp.json | Better Auth
| Clerk None. No extra agent tools from this one. |
| Footprint in your repo | Better 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
# 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
# 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)
- Account linking and email verification without account takeoversWhen "Continue with Google" joins an existing password account, when it refuses, and why an unverified email address or a trusted provider list can hand one person's account to another.docs/solutions/better-auth/account-linking-and-email-verification.md
- Better Auth on the edge: why your session check fails in middlewareEdge runtimes have no TCP sockets and no Node crypto, so a session lookup that works in a page throws in the proxy. Read the cookie there and verify in the render.docs/solutions/better-auth/better-auth-on-the-edge.md
- CSRF, SameSite and the cookie flags that make a session safeWhat each session cookie flag actually defends against, why trustedOrigins is your CSRF check, and the three configuration changes that quietly disable both.docs/solutions/better-auth/csrf-and-cookie-flags.md
- Guard Better Auth's endpoints, not just your settings formsEvery /api/auth endpoint is a public URL. One hook refuses account changes from an impersonation session and asks for a recent sign-in before a password or provider is added.docs/solutions/better-auth/guarding-the-auth-api-itself.md
- The magic-link token: single use, ten minutes, and the scanner that clicks it firstHow long the credential lives, why a corporate mail scanner burns it before the human arrives, and why the rate limiter has to be backed by your database rather than by process memory.docs/solutions/better-auth/magic-link-tokens-and-scanners.md
- Moving an existing user table onto Better Auth without logging everyone outMap your columns to the four required tables, backfill ids and accounts, and let people migrate themselves on next sign-in instead of forcing a password reset.docs/solutions/better-auth/migrating-an-existing-user-table.md
- oauth-callback-url-mismatchesdocs/solutions/better-auth/oauth-callback-url-mismatches.md
- Password reset tokens that cannot be replayed, guessed or leakedOne hour, single use, answered the same way for every address, and kept out of logs, Referer headers and search results. What Better Auth does for you and the four things it cannot.docs/solutions/better-auth/password-reset-tokens.md
- Modelling roles you will not regret when the admin panel growsA role column, a ranked vocabulary in one file, and permission checks that name the action, not a boolean isAdmin scattered across forty components.docs/solutions/better-auth/role-modelling-for-the-admin-panel.md
- Session invalidation, or why everyone got logged out on deployA rotated secret, a changed cookie name or a wiped database invalidates every session at once. Here is what invalidates what, and how to revoke one user on purpose.docs/solutions/better-auth/session-invalidation-and-logged-out-on-deploy.md
Clerk (7)
- Clerk impersonation, the act claim, and what to lock while it is onWhen an admin signs in as a user from the Clerk dashboard, the session token carries an act claim. Read it on the server, show a banner, and refuse account changes until it ends.docs/solutions/clerk/clerk-impersonation-and-the-act-claim.md
- Protecting route handlers is not the same as protecting pagesA redirect is the right answer for a page and a terrible answer for fetch. Use 401 and 403 in route handlers, and never let the proxy be the only check.docs/solutions/clerk/protecting-handlers-vs-pages.md
- Clerk roles: publicMetadata or your own table?publicMetadata is free and instant but vendor state you cannot join on. A local roles table joins and audits but must be kept in sync. Pick per what you need to query.docs/solutions/clerk/roles-publicmetadata-vs-local-table.md
- Make Clerk's sign-in look native, in light and dark, with CSS variablesPoint Clerk's appearance variables at your design's CSS custom properties so the prebuilt components follow your theme and your dark mode, with no copied hex values.docs/solutions/clerk/styling-clerk-with-css-variables.md
- Testing a Clerk webhook locally without a tunnel round tripSign the payload yourself and POST it at localhost. You get replays, retry ids and bad-signature cases in one second instead of thirty.docs/solutions/clerk/testing-clerk-webhooks-locally.md
- The proxy matcher that also matches your static assetsA matcher like "/(.*)" runs auth on every CSS file, image and font. The symptoms are an unstyled site, a redirect loop, or a surprising invocation bill.docs/solutions/clerk/the-matcher-that-ate-your-static-assets.md
- Clerk user-sync webhooks: duplicates, retries and out-of-order eventsSvix retries and can deliver twice, and updates can arrive before creates. Make the write idempotent on the user id and reject stale payloads by timestamp.docs/solutions/clerk/webhook-idempotency-and-ordering.md
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.