Production SaaS Starter
Production SaaS Starter
Production-oriented B2B SaaS foundation with workspace-based multi-tenancy, Stripe billing, platform admin, RBAC, and i18n. This guide gets you from clone to running locally in under an hour.
Overview
The Production SaaS Starter is a workspace-based multi-tenant platform — not a minimal demo. It includes real patterns: PostgreSQL RLS, SECURITY DEFINER RPCs, Stripe webhook idempotency, dual RBAC layers, and a service-provider abstraction.
- Workspaces with memberships, invites, and slug-scoped URLs
- Stripe subscriptions, usage metering, promo codes, and enterprise deals
- Platform admin console with audit logs and user/workspace moderation
- Email/password + OAuth, 2FA, password reset via Resend
Stack: Next.js 16, React 19, Supabase, Stripe, Resend, next-intl with 4 locales.
Install dependencies
Install npm packages from the project root before configuring environment variables.
Run this once after cloning the repository:
npm installEnvironment setup
Configure .env.local before running the app. Create the file first, then add values section by section as you complete setup below.
Create your local env file
Copy .env.example → .env.local at the project root before continuing. Add Stripe and Resend values in the sections below. Supabase variables are configured in the Supabase setup section.
cp .env.example .env.localWindows: copy .env.example .env.local
App
RequiredYour deployed app URL — used for checkout redirects, email links, and OAuth callbacks.
Steps
- 1For local dev, set NEXT_PUBLIC_APP_URL=http://localhost:3000
- 2For production, set it to your Vercel deployment URL or custom domain
- 3Add the variable in Vercel → Project → Settings → Environment Variables
Environment variables
NEXT_PUBLIC_APP_URLRequiredPublic URL of your SaaS app (no trailing slash).
Where to find
Local: http://localhost:3000 · Production: your Vercel project domain or custom domain
Example: https://app.yourdomain.com
NEXT_PUBLIC_APP_NAMEBrand name shown in transactional emails.
Where to find
Choose any display name — no external service needed
Example: Acme SaaS
Allowed origins
Configure cross-origin access for CSRF validation and server actions.
ALLOWED_ORIGINSComma-separated list of frontend domains allowed to access APIs.
ALLOWED_ORIGINS=ALLOWED_ORIGINS=http://localhost:3000ALLOWED_ORIGINS=http://localhost:3000,https://example.comSupabase setup
Complete these Supabase steps in order — create your project, then add environment variables.
Complete these Supabase steps in order — create your project, then add environment variables.
2 steps — complete in order
Supabase setup
~5 minCreate your project and copy your Project ID
Create a Supabase project
Sign in at supabase.com, click New project, choose a name and region, set a database password, and wait for provisioning to finish.
Open Supabase DashboardCopy your Project ID
You will use this when building your API URL and configuring the app.
Supabase environment variables
Add these to .env.local after creating your Supabase project.
Both keys are located on the same page.
NEXT_PUBLIC_SUPABASE_URLYour project API URL.
If you have not created a project yet:
- Go to https://supabase.com/dashboard/org
- Open your organization, create a project, then copy the Project URL.
- Supabase Dashboard → Open your project → Settings → API → Project URL
NEXT_PUBLIC_SUPABASE_URL=https://YOURPROJECTID.supabase.coWhere to find it
Supabase Dashboard → Open your project → Settings → API → Project URL
NEXT_PUBLIC_SUPABASE_ANON_KEYPublic anon key for client-side Supabase Auth and queries.
Where to find it
Supabase Dashboard → Project Settings → API Keys → Legacy API Keys → anon public
SUPABASE_SERVICE_ROLE_KEYServer-only service role key — never expose to the client.
Where to find it
Supabase Dashboard → Project Settings → API Keys → Legacy API Keys → service_role
Super admin email
Add after Supabase credentials are configured.
SUPER_ADMIN_EMAILEmail address that should receive super admin permissions.
SUPER_ADMIN_EMAIL=SUPER_ADMIN_EMAIL=admin@example.comStripe setup
Stripe CLI
Use the Stripe CLI for local authentication and webhook forwarding during development.
- 1Install Stripe CLI using the instructions from the official Stripe documentation
- 2Complete the platform-specific installation steps
- 3Run Stripe authentication/login
- 4Continue with local Stripe development after authentication
Authenticate the CLI
Run login — a browser window opens to connect your Stripe account:
stripe loginForward webhooks locally
First run:
stripe listen --forward-to localhost:3000/api/stripe/webhookThen copy the generated signing secret (whsec_...) from the terminal output:
whsec_xxxxxPaste it into .env.local:
STRIPE_WEBHOOK_SECRET=whsec_xxxxxAPI keys
Open the Stripe Dashboard and copy your keys into .env.local — never hardcode keys in the repository or documentation.
Open Stripe Dashboard- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY — copy the Publishable key
- STRIPE_SECRET_KEY — copy the Secret key
Resend setup
Resend
RequiredTransactional email — invites, 2FA OTP, password reset. Supabase built-in email is not used.
Steps
- 1Sign up at resend.com
- 2Add and verify your sending domain (DNS records)
- 3Create an API key
- 4Set RESEND_FROM_DOMAIN to your verified sending domain (see Resend setup below)
Environment variables
RESEND_API_KEYRequiredAPI key for sending emails.
Where to find
Resend Dashboard → API Keys → Create API Key
RESEND_FROM_DOMAINMust contain your verified Resend domain. Use the root/origin domain only — not a subdomain.
RESEND_FROM_DOMAIN=RESEND_FROM_DOMAIN=shipy.liveQuick start
After completing environment, Supabase, Stripe, and Resend setup above, run:
Sync seeded plans to Stripe — mainly for testing and keeping Stripe plans in sync with the database
npm run sync:plans🎉 Congratulations!
Everything is now configured. Start the application:
npm run devYou're ready to go!
Development vs production
Use different commands for day-to-day development and for previewing a production build locally.
Development
Start the development server with hot reload:
npm run devProduction preview
Preview the production environment locally — build the app, then start the production server:
npm run buildnpm run startScripts reference
| Command | Description |
|---|---|
| npm run sync:plans | Sync seeded plans to Stripe products/prices |
| npm run dev | Start development server |
| npm run build | Production build |
| npm run start | Start production server (after build) |
| npm run test | Run Jest unit tests (services only) |
Architecture
Four layers with strict call order — agents and developers follow the same path:
UI → Server Actions / API → Services → Repositories → PostgreSQL (RLS/RPC)Key locations:
Layer 1
Presentation — src/app/, src/components/
Layer 2
Application — server actions, createRequestContext(), RBAC guards
Layer 3
Domain — src/modules/*/ *.service.ts (business rules)
Layer 4
Data — repositories + PostgreSQL RLS/RPCs via src/services/
Multi-tenancy
A workspace is the tenant — there is no separate organization table.
- Tenant key: workspaces.id (UUID); URLs use unique slug
- Memberships link users to workspaces with owner, admin, or member roles
- RLS policies enforce workspace_id IN (user's memberships)
Platform admins access /admin via platform_admins table with super_admin, platform_admin, or platform_viewer roles.
Authentication & authorization
Auth flow: login → optional 2FA → onboarding (no workspace) or workspace dashboard. Platform admin invites route to platform-onboarding.
- Email/password and OAuth via Supabase Auth
- 2FA via email OTP (Resend) — not Supabase built-in email
- Password reset with token-based flow and rate limiting
Workspace roles and permissions:
ownerbilling, delete workspace, full member managementadmininvite/remove members, update workspace settingsmemberread and participate — no admin actions
Billing & subscriptions
One active subscription per workspace, tied to Stripe customer and subscription IDs.
- Checkout for new subscriptions; portal for self-serve management
- Upgrades with proration; downgrades scheduled at period end
- Seeded plans: Starter ($29/mo), Pro ($39/mo), Enterprise (custom)
- Webhook at /api/stripe/webhook syncs state with idempotent stripe_events table
Deployment
Recommended stack: Vercel (Next.js) + Supabase Cloud + Stripe webhooks.
- 1Set all env vars in Vercel — use the Environment setup section above
- 2Deploy the Next.js app
- 3Register Stripe webhook → https://YOUR_APP_URL/api/stripe/webhook
- 4Bootstrap super admin and run sync:plans in production

