Boring Docs

Config & Env — One Switch Per Concern

Every env var, what it does, and safe defaults for local vs prod.

Config & Env

Copy once: cp .env.example .env.local. Prod uses .env.prod (see npm run build:prod / start:prod). Never commit secrets — .gitignore covers .env*, *.pem, atlas-credentials.env.

1. Core + auth

  • NEXTAUTH_URLhttp://localhost:3000 local; prod domain in .env.prod.
  • NEXTAUTH_SECRET — any random 32+ chars.
  • ADMIN_EMAILS — comma-separated owner emails. Prod: must be set (no dev fallback — see libs/admin.js). Dev fallback: [email protected].
  • GOOGLE_ID, GOOGLE_SECRET — optional Google OAuth. Without them, use dev login locally.
  • EMAIL_SERVER, RESEND_API_KEY, RESEND_FROM — optional magic-link + transactional email via Resend.

2. Database (one switch)

DB_PROVIDER=sqlite
SQLITE_PATH=./db/app.db
# DB_PROVIDER=mongodb
# MONGODB_URI=mongodb+srv://<user>:<pass>@cluster/boring
# DB_PROVIDER=supabase
# SUPABASE_URL=https://<ref>.supabase.co
# SUPABASE_SERVICE_KEY=<service_role>

Rules: only service_role on server, never NEXT_PUBLIC_*. Supabase needs schema.sql run once. SQLite prod path: SQLITE_PATH=/db/app.db with a mounted volume.

3. Billing (one switch)

BILLING_PROVIDER=dodo
DODO_MODE=test
DODO_PAYMENTS_API_KEY=
DODO_PRODUCT_ID=pdt_xxx
DODO_WEBHOOK_KEY=
# STRIPE_* / LS_* alternatives documented in .env.example

Price IDs live in config/billing.js (sale, dodo.plans, stripe.plans) with env overrides. Never trust client prices — checkout pulls from the product row.

4. Analytics + delivery

  • ANALYTICS_ENABLED=true, ANALYTICS_DEBUG=false, optional ANALYTICS_PATH for tests.
  • GITHUB_ACCESS_TOKEN, GITHUB_REPO=owner/repo — optional auto-invite for buyers via /api/claim-repo.

5. App constants

Brand/prices in config/; toggles/limits/statuses in libs/config.js (DB_PROVIDER, BILLING_PROVIDER, POST_STATUSES, LIMITS). Never hardcode the same literal twice.