auth · side by side
Clerk vs Supabase Auth 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 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.
Pick Supabase Auth if
Teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows.
Clerk is tested with Neon. Supabase Auth isn't yet.
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 AClerk | Option BSupabase Auth |
|---|---|---|
| In one line | Clerk Hosted auth with prebuilt sign-in UI in your design's colours and a user sync webhook. | Supabase Auth Postgres-native auth where the database, not the API layer, is the last line of defence. |
| Pricing | 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. | Supabase Auth Free: 50,000 monthly active users. Pro: 100,000 included, then $0.00325 per MAU. Anonymous sign-ins are included. SAML SSO needs Pro: 50 SSO users included, then $0.015 each. |
| Best for | 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. | Supabase Auth Teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows. |
| Trade-offsVerbatim from the manifest | Clerk
| Supabase Auth
|
| Required companionsAdded for you, with a reason | Clerk Nothing. It stands on its own. | Supabase Auth
|
| Recommended alongsideSuggested, never added for you | Clerk
| Supabase Auth
|
| Env vars you will manageEvery one documented in docs/onboard.md | Clerk 4 variables · 2 required
| Supabase Auth 0 variables None. |
| Dependencies added | Clerk
| Supabase Auth
|
| MCP serversWritten into .mcp.json | Clerk None. No extra agent tools from this one. | Supabase Auth None. No extra agent tools from this one. |
| Footprint in your repo | Clerk 29 files, plus 8 injections into shared stack files | Supabase Auth 60 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 Clerk (19)
src/9 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/2 files
auth/2 files
- clerk-root.tsx
- clerk-user-profile.tsx
lib/4 files
auth/4 files
- appearance.ts
- clerk-env.d.ts
- publishable-key.ts
- roles.ts
variants/10 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/3 files
slots/1 file
- db-schema.ts
src/2 files
db/1 file
- clerk-schema.ts
lib/1 file
auth/1 file
- user-store.ts
orm-prisma/2 files
slots/1 file
- prisma-models.prisma
src/1 file
lib/1 file
auth/1 file
- user-store.ts
Only with Supabase Auth (50)
scripts/1 file
auth/1 file
- seed.ts
src/44 files
app/9 files
(supabase-auth)/6 files
forgot-password/1 file
- page.tsx
no-access/1 file
- page.tsx
reset-password/1 file
- page.tsx
sign-in/1 file
- page.tsx
sign-up/1 file
- page.tsx
- layout.tsx
auth/3 files
callback/1 file
- route.ts
confirm/1 file
- route.ts
sign-out/1 file
- route.ts
components/16 files
auth/16 files
settings/6 files
- connected-accounts.tsx
- email-card.tsx
- form-feedback.tsx
- password-form.tsx
- profile-form.tsx
- sessions-card.tsx
- auth-card.tsx
- check-email.tsx
- forgot-password-form.tsx
- oauth-buttons.tsx
- password-input.tsx
- provider-icons.tsx
- reset-password-form.tsx
- sign-in-form.tsx
- sign-up-form.tsx
- supabase-session-listener.tsx
lib/19 files
auth/19 files
- actions.ts
- admin.ts
- client.ts
- constants.ts
- database.types.ts
- env.ts
- errors.ts
- flow.ts
- form-state.ts
- identity.test.ts
- impersonation.ts
- profile.ts
- provider-settings.test.ts
- provider-settings.ts
- providers.ts
- proxy.test.ts
- rls.ts
- schemas.ts
- server.ts
supabase/2 files
migrations/2 files
- 0100_supabase_auth_profiles.sql
- 0101_supabase_auth_profile_sync.sql
tests/1 file
e2e/1 file
- auth.setup.ts
variants/2 files
orm-prisma/2 files
slots/1 file
- prisma-external-tables.ts
supabase/1 file
migrations/1 file
- 0102_prisma_profiles_without_cross_schema_fk.sql
Same path, different implementation (10)
src/8 files
components/3 files
auth/3 files
- account-settings.tsx
- header-auth-actions.tsx
- sign-out-button.tsx
lib/5 files
auth/5 files
- origin.ts
- proxy.ts
- redirect.test.ts
- redirect.ts
- session.ts
tests/2 files
e2e/2 files
- auth.spec.ts
- auth.ts
Shared stack files Clerk injects into
- bare-route-groups
- env-required
- header-actions
- legal-processors
- middleware-matchers
- providers
- proxy-handlers
- verify-checks
Shared stack files Supabase Auth injects into
- bare-route-groups
- env-required
- header-actions
- legal-processors
- middleware-matchers
- providers
- proxy-handlers
- verify-checks
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
Supabase Auth in your .env.local
# Supabase Auth adds no environment variables.
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/**.
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.
Supabase Auth
3
Skills
4
Rules
7
Solution docs
Rules (4)
Every table needs row-level security and at least one policy
supabase/** · src/lib/auth/**
getUser() is the trust boundary, getSession() is not
src/lib/auth/** · src/app/** · src/components/**
The service-role key is a root password
src/lib/auth/** · src/app/** · supabase/**
Sign-in flows keep return paths safe and reveal nothing about accounts
src/components/auth/** · src/lib/auth/** · src/app/(supabase-auth)/** · src/app/auth/**
Skills (3)
/add-auth-rls-policy
Add row-level security policies to a Supabase table (owner-scoped, tenant-scoped or admin) with the indexes and the tests that prove they work.
/add-oauth-provider
Switch on Google, GitHub or Microsoft sign-in for Supabase Auth, or add a new provider (Apple, Discord, LinkedIn) with its button, icon, redirect URLs and profile mapping.
/add-profile-field
Add a field people edit on /settings/profile (a bio, a time zone, a company) with Supabase Auth, stored in public.profiles behind a column grant, validated with zod and saved by a server action.
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.
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
Supabase Auth (7)
- Logged out after an hour: Supabase cookie refresh in the App RouterAccess tokens expire hourly and Server Components cannot write cookies, so the refresh has to happen in the proxy and be returned on the same response object.docs/solutions/supabase-auth/cookie-refresh-in-the-app-router.md
- getSession() vs getUser(): the Supabase trust trapgetSession() decodes a cookie the browser controls; getUser() verifies it with the auth server. On the server, only one of them is a security check.docs/solutions/supabase-auth/getsession-vs-getuser.md
- Admin impersonation on Supabase Auth, bound to one sessionSupabase Auth has no "view as user". Build it from a server-side magic link plus an app_metadata marker tied to the new session id, so only that session is flagged and nobody can forge it.docs/solutions/supabase-auth/impersonation-bound-to-one-session.md
- Migrating an app that only ever used the anon keyTables with RLS off are public. Turn it on table by table behind a feature switch, write the policies, and fix the queries the policies break, in that order.docs/solutions/supabase-auth/migrating-from-anon-key-only-access.md
- Show only the OAuth buttons your Supabase project has switched onRead the public /auth/v1/settings endpoint on the server, cache it, and fail closed, so a sign-in page never shows a Google button that ends on an error page.docs/solutions/supabase-auth/oauth-buttons-from-auth-settings.md
- RLS policy patterns for multi-tenant rowsOwner-scoped, org-scoped and role-scoped policies, the with-check clause people forget, and the indexes that stop a policy from turning every read into a scan.docs/solutions/supabase-auth/rls-patterns-for-multi-tenant-rows.md
- The blast radius of a leaked Supabase service-role keyThe key bypasses every policy for every table. Here is how it leaks, what an attacker gets, how to contain it, and how to make the leak impossible.docs/solutions/supabase-auth/service-role-key-blast-radius.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 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.
Pick Supabase Auth when
Teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows.
And accept that(5)
- Authorization lives in SQL policies, not in TypeScript. That is the whole point, and it is also the learning curve: you debug permissions with
explainandset role, not a debugger. - Auth is coupled to the Supabase project. Moving the database off Supabase means moving users, JWT signing and every policy at the same time.
- Cookie-based sessions in the App Router need a proxy refresh. Skip it and users get logged out after an hour with no error anywhere.
- Roles live in
app_metadata, which only the service-role key can write. Good for security, awkward for self-service role changes. - The service-role key bypasses every policy. One import of it into a client component and the whole database is public.
Questions people actually ask
- Should I choose Clerk or Supabase Auth?
- 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. Supabase Auth is best for teams already on Supabase Postgres who want row-level security as the authorization model. With the policies right, a buggy query cannot return another tenant's rows. Both fill the auth slot, so a generated repo carries one or the other, never both.
- How much do Clerk and Supabase Auth cost?
- 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. Supabase Auth: Free: 50,000 monthly active users. Pro: 100,000 included, then $0.00325 per MAU. Anonymous sign-ins are included. SAML SSO needs Pro: 50 SSO users included, then $0.015 each.
- What changes in my repo if I switch from Clerk to Supabase Auth?
- Clerk writes 29 files, 4 environment variables and 2 dependencies, and installs 3 path-scoped rules, 2 skills and 7 solution docs. Supabase Auth writes 60 files, 0 environment variables and 4 dependencies, and installs 4 path-scoped rules, 3 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.