Next.js Boilerplate with Better Auth and Drizzle: Why This Stack

September 6, 2026
Why a Next.js boilerplate on Better Auth and Drizzle ORM ages well: verified features, fair NextAuth and Prisma comparisons, and how NEXTY.DEV wires them.
Next.js Techniques
Database

The two decisions you cannot cheaply undo

Most of what a SaaS boilerplate ships is replaceable. Swap the UI kit, change the email provider, move from Vercel to a VPS: annoying, but a week of work at most. Two things are not like that: the authentication layer and the ORM. A Next.js boilerplate with Better Auth and Drizzle is a bet on exactly those two, so it is worth understanding what the bet is before you build twelve months of product on top of it.

Auth is expensive to change because the user, session and account tables are referenced by everything: orders, credits, posts, audit logs, team memberships. The ORM is expensive to change because every query in the codebase is written against it, and migrations history is tied to its tooling. NEXTY.DEV went through this once, moving from Supabase Auth and Supabase Database to Better Auth and Drizzle ORM in v3.0.0. That migration is the reason this post exists: we know what the switch costs, and we know why we would not go back.

What Better Auth gives a SaaS boilerplate

Better Auth describes itself as a framework-agnostic, universal authentication and authorization framework for TypeScript. The core ships email/password and social sign-in, and everything beyond that is a plugin. The parts a SaaS actually needs, all verified against the official docs:

  • Magic link: a link sent to the user's email; clicking it authenticates them, no password involved.
  • Email OTP: a one-time code sent to the email address, usable for sign-in, email verification and password reset.
  • Google One Tap: single-tap login through Google's One Tap API, client and server side handled by the plugin.
  • Two-factor: TOTP from an authenticator app, OTP by email or phone, backup codes for recovery, trusted devices.
  • Organization: organizations, members, email invitations with expiry, three default roles (owner, admin, member), custom roles, teams, and an access-control system you can extend.
  • Admin: create users, change roles, ban and unban (temporary or permanent), impersonate, list users, revoke sessions.

The design principle underneath all of it is "your auth lives in your codebase". Better Auth writes to four core tables you own (user, session, account, verification) plus whatever tables the plugins you enable add, and it generates that schema for your ORM with npx auth@latest generate.

What it replaces, and the fair version of the tradeoff

NextAuth / Auth.js. Auth.js is a mature, runtime-agnostic library with over 100 OAuth providers and adapters for Prisma, Drizzle, Supabase, Kysely and more. It supports OAuth, magic links, credentials and WebAuthn. Where the two differ is depth of the SaaS-specific layer: Auth.js gives you a session and a user; organizations, roles, bans, 2FA and impersonation are yours to build. Session strategy is the other practical difference. Auth.js defaults to JWT sessions unless you configure a database adapter, and its own docs are candid that "expiring a JSON Web Token before its encoded expiry is not possible" without a server-side blocklist, while database sessions "can be at any time modified server-side" at the cost of a database roundtrip. Better Auth is database-session by default, which is what you want when a SaaS admin needs to ban a user and have it take effect now.

Hosted auth (Supabase Auth, Clerk, Auth0). The honest case for a hosted provider is that someone else runs it: password hashing, OAuth callback edge cases, email deliverability, and a dashboard your support team can use on day one. The cost is that user identity lives in someone else's schema and pricing tier, and every feature that touches users pays an extra network hop or maintains a shadow copy. With Better Auth the user table is a normal table in your Postgres, joinable in a single query with orders or credit_transactions. For a small team that already operates a database, that is less infrastructure, not more.

Concretely, a SaaS needs: social login, a passwordless path for people who hate passwords, roles for the admin dashboard, the ability to kill a session, and eventually teams. Better Auth covers all five without leaving your codebase.

What Drizzle ORM gives a SaaS boilerplate

Drizzle's own pitch is "if you know SQL, you know Drizzle". It is a TypeScript ORM with exactly zero dependencies, about 31 KB, described as serverless-ready by design, running over industry-standard drivers for PostgreSQL, MySQL, SQLite and others. The schema is TypeScript, the query builder mirrors SQL, and the relational query API compiles to exactly one SQL query. Migrations come from drizzle-kit: drizzle-kit generate diffs your schema against the previous snapshot and writes SQL; drizzle-kit migrate applies it.

A table and a query, nothing exotic:

import { pgSchema, text, timestamp, integer, uuid } from 'drizzle-orm/pg-core'
import { eq, desc } from 'drizzle-orm'

// One Postgres schema per project keeps several apps on one database
export const app = pgSchema('myapp')

export const orders = app.table('orders', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: text('user_id').notNull(),
  amountCents: integer('amount_cents').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
})

// Fully typed: rows come back as { id: string; userId: string; amountCents: number; createdAt: Date }[]
const recent = await db
  .select()
  .from(orders)
  .where(eq(orders.userId, session.user.id))
  .orderBy(desc(orders.createdAt))
  .limit(20)

Drizzle vs Prisma for a SaaS

Prisma is the other serious choice, and a good one. It has its own declarative schema language, an auto-generated type-safe client, Prisma Migrate and Prisma Studio; the docs state Prisma Client runs in any supported Node.js or TypeScript backend including serverless. Its runtime has changed substantially in the last two years, so re-verify any old comparison built around engine binaries against prisma.io for your target runtime.

The difference that matters day to day is philosophy. Prisma abstracts SQL behind its own query language and schema file; Drizzle keeps you one step from SQL and the schema is plain TypeScript, so the same file that defines a table also exports the types and can carry indexes, partial indexes, and ON DELETE semantics without leaving the language. For a SaaS where you will write a credit_transactions ledger with sequence columns and partial indexes on next_grant_at, being close to SQL stops being a stylistic preference and becomes a practical one.

Why the two pair well

Both are TypeScript-first. Both put the data in your database with no vendor in the loop. And Better Auth has an official Drizzle adapter: drizzleAdapter(db, { provider: 'pg' }), with a schema option to map Better Auth's models onto your Drizzle tables. The adapter expects your table names to match, and the CLI generates the Drizzle schema for you, so the auth tables become ordinary rows in the same schema.ts as your billing tables. One migrations history, one set of types, one place to look.

How NEXTY.DEV wires Better Auth and Drizzle

NEXTY.DEV is a Next.js SaaS boilerplate that has run this pairing in production since v3.0.0. The changelog says the move from Supabase Auth was for greater flexibility, and the move from Supabase Database to Drizzle was for a better development experience; both held up.

Auth methods. Google and GitHub OAuth, email OTP and magic link, with Cloudflare Turnstile on the forms and IP plus per-user rate limiting on login. The Better Auth admin plugin backs the admin dashboard: user roles, banning (which also clears the banned user's sessions), and a user list with source attribution. The organization plugin drops into the same config when you need teams; nexty.dev itself runs it for seat-capped team invitations. The server side exposes small guards (getSession, isAdmin) so protected pages and server actions do not re-implement session checks. The only secret you must generate is BETTER_AUTH_SECRET; the Better Auth secret generator produces one in the browser.

Database. Fill in DATABASE_URL and the connection factory picks pool settings by platform and provider. Supabase, Neon and self-hosted Postgres are supported; on Supabase's pooled connection prepared statements are disabled automatically, which matters because PgBouncer in transaction mode will otherwise fail silently. All tables, auth included, live in one schema.ts and one drizzle-kit migrations directory.

Schema isolation. Every table is declared under pgSchema('nexty') rather than public, so several projects can share one Postgres instance without table-name collisions, and moving a project between databases is a schema dump, not a surgery. The full procedure is in database schema isolation and migration.

The rest of the SaaS. Stripe (and Creem, PayPal) with subscriptions, one-time payments and a credit ledger; next-intl with en, zh and ja out of the box; a CMS with multilingual posts; Resend or Cloudflare Email Sending; R2 file storage; AI SDK demos. The Pro license is $188 one-time with lifetime updates and access to the private source repository; the open-source plan is a free, functionality-limited starter that ships i18n, newsletter, analytics and a static blog but no database, auth or payments. Details at pricing and the introduction docs.

Migration notes if you are already on Supabase Auth or NextAuth

Be realistic: this is a data migration plus a code migration, and the data part is the easy half.

From Supabase Auth. Export auth.users and identities, insert into Better Auth's user and account tables (one account row per linked provider). Password hashes are the problem: Supabase stores bcrypt, Better Auth defaults to scrypt. Either configure Better Auth's password hashing to verify the legacy format, or force a reset via magic link on first login. Sessions do not migrate; everyone signs in again. Budget the most time for replacing supabase.auth.getUser() calls and RLS-dependent queries, since Drizzle talks to Postgres directly and RLS policies keyed on auth.uid() no longer apply.

From NextAuth / Auth.js. The table shapes are close (user, account, session, verification token), so a column-mapping script covers most of it. If you were on JWT sessions there is nothing to migrate; users re-authenticate. The larger cost is code: every getServerSession and useSession becomes a Better Auth call, and any role logic you bolted on becomes the admin plugin's role field.

Either way. Run the new auth in a separate Postgres schema first, migrate a copy, and diff counts before you flip DNS. It is a two-to-five-day job for a typical SaaS, not an afternoon.

FAQ

Is Better Auth production-ready compared with NextAuth?

Both are used in production. Auth.js has the longer history and broader provider list; Better Auth has the deeper built-in SaaS layer (organizations, admin, 2FA, bans) and database sessions by default. Choose on what you need to own versus build.

Can I use Drizzle with Supabase or Neon, or only self-hosted Postgres?

All three. Drizzle runs over standard Postgres drivers. The one provider-specific detail is Supabase's pooled connection string, which needs prepared statements disabled; NEXTY.DEV does that for you.

Does a Better Auth and Drizzle boilerplate work on serverless and Cloudflare Workers?

Drizzle is serverless-ready by design and Better Auth is framework-agnostic. NEXTY.DEV deploys to Vercel, Cloudflare Workers, Dokploy and Coolify; the Workers path uses an HTTP driver or Hyperdrive for the database connection.

What is the license model?

Pro is a one-time $188 payment with lifetime updates; you are invited as a collaborator on the private repository and keep access to future versions.

Closing

If you are starting a SaaS in 2026 and you want user identity and data to stay in your own Postgres, with types that follow you from schema to query to session, Better Auth plus Drizzle is the pairing we would pick again. NEXTY.DEV packages that choice with payments, i18n, CMS and an admin dashboard already wired. Read the introduction, compare the plans, and start from a schema you will not have to rip out.