Production-minded, multi-tenant SaaS starter by Nexlabz. MIT licensed.
- Next.js 16 App Router, TypeScript, Tailwind v4
- Better Auth: email + password, optional Google, organizations, roles, invitations
- Postgres + Drizzle: committed migrations, row-level security on tenant tables
- Polar: subscriptions and one-time purchases, billed per organization, sandbox first
- SEO: metadata, Open Graph image, sitemap, robots, JSON-LD
- Vercel Analytics + Speed Insights, typed env validation, health endpoint, tests, CI
Only a Postgres database is required. Email, Google login and payments switch on when you add their keys.
Use the GitHub template:
gh repo create my-app --template nexlabz/starter --private --clone
cd my-appOr copy it without git history:
npx degit nexlabz/starter my-appOr deploy straight to Vercel:
Needs Node 22+, pnpm and Docker.
cp .env.example .env # then set BETTER_AUTH_SECRET: openssl rand -base64 32
pnpm install
pnpm db:up # Postgres on localhost:5433
pnpm db:migrate
pnpm devOpen http://localhost:3000, sign up, create a workspace. Emails print to the terminal until you add Resend.
Only the first two keys are required. Everything else has a safe behaviour when unset, and /api/health shows the billing and email mode of any environment.
| Key | Required? | Without it |
|---|---|---|
DATABASE_URL |
Required | The server refuses to boot in production |
BETTER_AUTH_SECRET |
Required | The server refuses to boot in production |
NEXT_PUBLIC_APP_URL |
Custom domains | Uses the Vercel production domain, or http://localhost:3000. Requests from any other origin fail sign-in with "Invalid origin" |
RESEND_API_KEY |
Recommended in production | No email goes out. Locally it prints to the terminal. In production, invites still work: copy the link from the Team page |
EMAIL_FROM |
With Resend | Uses Resend's test sender, which only delivers to your own address |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
Optional, set both | The "Continue with Google" button is hidden |
POLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET |
Optional, set both | Billing is off: everyone is on Free and checkout buttons are disabled |
POLAR_PRODUCT_* |
With Polar | That plan's button shows "Product not set" |
POLAR_SERVER |
With Polar | Uses sandbox, so set production to take real payments |
POSTGRES_PORT |
Optional | Local Docker Postgres uses port 5433 |
Details and where to get each key are in docs/SETUP.md.
| Doc | Covers |
|---|---|
| docs/SETUP.md | Every account and env var |
| docs/MULTI-TENANCY.md | How tenants work, who sees which page, adding a table |
| docs/BILLING.md | Sandbox in 5 minutes, changing plans, going live |
| docs/DEPLOYING.md | Vercel, migrations, previews |
dev · build · lint · typecheck · test · db:up · db:down · db:generate · db:migrate · db:studio · polar:seed
src/config/site.ts: name, tagline, linkssrc/app/globals.css: colour tokenssrc/lib/billing/catalog.ts: plans and limits- Replace the sample
projecttable with your own, following docs/MULTI-TENANCY.md