Skip to main content
Skip to content

Last updated on 4 June 2026

Installation

This template ships as source code. Extract the ZIP, set env vars, install deps, migrate the database, seed demo data, and run the dev server. The whole flow takes under 10 minutes on a laptop with Docker.

Prerequisites

  • Node.js 22.13.x (engine range >=22.13.0 <23, matching CI and the pinned pnpm runtime)
  • pnpm 10+npm install -g pnpm@10 or use corepack
  • Docker Desktop — used to start Postgres in the demo flow (optional if you have Postgres installed natively)
  • Git — for cloning and version control

The demo compose runs Postgres only (exposed on host port 5431). The Next.js app runs on the host via pnpm dev so hot-reload and source edits work normally.

bash
# 1. Copy environment
cp .env.example .env

# 2. Start Postgres in the background (pulls image on first run)
docker compose -f docker-compose.demo.yml up -d

# 3. Install JS deps
pnpm install

# 4. Apply migrations (the script waits for Postgres to become reachable)
pnpm db:migrate

# 5. Seed demo users and billing plans
pnpm db:seed

# 6. Run the dev server
pnpm dev

# 7. Open http://localhost:3000 in your browser

PowerShell users can run the same flow with Copy-Item .env.example .env; the remaining commands are identical.

If Next.js reports a different local URL because port 3000 is already in use, either stop the other process and restart pnpm dev, or set both PORT and NEXT_PUBLIC_APP_URL in .env to the URL you are actually using.

Demo login

  • Regular user: demo@example.com / Password123!!
  • Admin: admin@example.com / Password123!!

If DEMO_MODE=true and you deploy publicly, set SEED_PASSWORD in .env to override the default password — otherwise your admin account is trivially guessable.

Quick Start (native Postgres)

If you already have Postgres running locally:

bash
# 1. Create the database
createdb saas_starter

# 2. Copy env and point DATABASE_URL at your local instance
cp .env.example .env
# Edit .env: DATABASE_URL="postgresql://<user>:<pass>@localhost:5432/saas_starter"

# 3. Install + migrate + seed + dev
pnpm install
pnpm db:migrate
pnpm db:seed
pnpm dev

PowerShell equivalent for the copy step: Copy-Item .env.example .env.

Environment Variables

The .env.example file documents every variable the app reads. For local development the defaults work — for production, see DEPLOYMENT.md for required variables and secret-generation commands.

Must set in production:

  • DATABASE_URL — Postgres connection string
  • JWT_SECRET — min 32 chars, openssl rand -base64 48
  • OAUTH_STATE_SECRET — min 32 chars, openssl rand -hex 32 (required if OAuth enabled)
  • SMTP_HOST / SMTP_FROM — transactional email sender for verification, reset, and magic-link flows
  • WEBHOOK_ENCRYPTION_KEY — 64 hex chars, openssl rand -hex 32
  • NEXT_PUBLIC_APP_URL — your canonical public URL
  • STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET — required if billing is enabled

Verifying the install

After seeding, the following should all work:

  • http://localhost:3000 renders the landing page
  • http://localhost:3000/login accepts demo@example.com / Password123!!
  • http://localhost:3000/dashboard loads after login
  • http://localhost:3000/health returns { status: "healthy" } (200)
  • http://localhost:3000/ready returns { status: "ready", database: "connected" } (200)
  • http://localhost:3000/api/docs renders the API reference

Verifying account creation and OAuth

Self-registration works in local development after the quick-start commands above. New accounts receive a verification email first; in local development the verification link is logged to the dev console unless you configure SMTP. After verification, the user can sign in normally.

OAuth works after you add real provider keys to .env and restart pnpm dev. Use callback URLs that match NEXT_PUBLIC_APP_URL exactly:

  • Google: http://localhost:3000/api/auth/oauth/google/callback
  • GitHub: http://localhost:3000/api/auth/oauth/github/callback

If you run the dev server on another port, update PORT, NEXT_PUBLIC_APP_URL, and the provider callback URLs to that same port.

Verifying Stripe billing webhooks

For local billing tests, install and authenticate the Stripe CLI, then forward events to the template's webhook route:

bash
stripe listen --forward-to http://localhost:3000/api/webhooks/stripe

Copy the printed whsec_... value into .env alongside your Stripe secret key. Both are required: STRIPE_SECRET_KEY lets the route process Stripe objects, and STRIPE_WEBHOOK_SECRET verifies the Stripe-Signature header.

env
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."

If you run the dev server on another port, update the stripe listen URL to the same port, then restart the dev server after editing .env. In production, create a Stripe webhook endpoint at:

text
https://your-domain.com/api/webhooks/stripe

Pre-flight checks

The buyer ZIP ships as a clean runnable starter. Run the shipped app-level checks before customising:

bash
pnpm typecheck        # TypeScript strict mode
pnpm lint             # ESLint (zero warnings)
pnpm build            # Production build
pnpm audit:prod       # Dependency vulnerability scan

Common issues

pnpm install fails on native bindings

Some deps compile native bindings. Install Python 3 and a C++ toolchain:

  • macOS: xcode-select --install
  • Ubuntu/Debian: sudo apt install build-essential python3
  • Windows: install Visual Studio Build Tools with "Desktop development with C++"

Migrations fail with PostgreSQL is not reachable

docker compose up -d starts the container, but Postgres may still be warming up or the host port may not be reachable yet. The pnpm db:migrate and pnpm db:seed scripts wait up to 60 seconds before failing. If they still fail, inspect the container and port mapping:

bash
docker compose -f docker-compose.demo.yml ps
docker compose -f docker-compose.demo.yml logs postgres

Confirm .env matches the exposed compose port (POSTGRES_PORT) and that no other local Postgres/container is already bound to that port.

Migrations fail with relation does not exist

pnpm db:migrate is the correct first-run command. The template ships with versioned migrations in src/lib/db/migrations/. If migrations fail, the database connection is probably wrong (check DATABASE_URL in .env matches the Postgres the compose/host is actually running). pnpm db:push is an escape hatch for schema experimentation only and skips version tracking.

OAuth callback fails with OAUTH_STATE_SECRET is required

Uncomment the line in .env.example (it's uncommented in .env.example itself; if you still see this error, ensure you have it in your .env and restart the dev server).

Port 3000 already in use

Set PORT=3001 (or any free port) in .env, or stop the conflicting process: lsof -ti:3000 | xargs kill.

Next steps

  • CUSTOMIZATION.md — theme, brand name, colours, fonts, demo content, legal pages
  • DEPLOYMENT.md — production hosting, Docker, health checks, cron, OAuth setup
  • LICENCE.md — commercial licence terms