Production-shaped foundation for launching a startup SaaS baseline quickly.
- Frontend: Next.js (TypeScript)
- UI: shadcn/ui (Tailwind + Radix)
- Backend: Go (
net/http) - Database: Postgres
- Cache: Redis (Upstash in cloud, local Redis in development)
- Auth: Clerk (managed auth, organization context)
- Billing: Stripe (checkout + portal + webhook sync)
- Deploy: Render (backend + Postgres) + Vercel (frontend)
- CI: GitHub Actions
frontend/Next.js shell and app routesbackend/Go API shell, auth/billing endpoints, and migrationsdocker-compose.ymllocal Postgres + Redisrender.yamlRender blueprint for backend and Postgres.github/workflows/ci.ymlCI for frontend and backenddocs/roadmap.mdproduct phases after shell
- Node.js 20+
- npm 10+
- Go 1.22+
- Docker Desktop (or Docker Engine + Compose)
Copy examples and adjust as needed:
cp .env.example .env
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env.localCore variables:
- Backend
PORT(default8080)DATABASE_URL(local Postgres or Render Postgres URL)REDIS_URL(local Redis or Upstash Redis URL)APP_BASE_URL(frontend URL used for checkout return paths)APP_ENV(developmentorproduction)APP_VERSION(dev, commit SHA, or release tag)OTEL_SERVICE_NAME(defaultsaas-core-template-backend)OTEL_TRACES_EXPORTER(console,otlp, ornone)OTEL_EXPORTER_OTLP_ENDPOINT(local collector defaulthttp://localhost:4318)OTEL_EXPORTER_OTLP_HEADERS(for managed OTLP auth, e.g. Grafana Cloud)ERROR_REPORTING_PROVIDER(console,sentry, ornone)SENTRY_DSN(backend error reporting)SENTRY_ENVIRONMENT(defaults to empty)ANALYTICS_PROVIDER(console,posthog, ornone)POSTHOG_PROJECT_KEYPOSTHOG_HOSTEMAIL_PROVIDER(console,resend, ornone)EMAIL_FROMRESEND_API_KEYJOBS_ENABLED(worker toggle)JOBS_WORKER_IDJOBS_POLL_INTERVALFILE_STORAGE_PROVIDER(disk,s3, ornone)FILE_STORAGE_DISK_PATHS3_BUCKET,S3_REGION,S3_ENDPOINT,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_FORCE_PATH_STYLECLERK_SECRET_KEYCLERK_API_URL(defaulthttps://api.clerk.com)STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_API_URL(defaulthttps://api.stripe.com/v1)STRIPE_PRICE_PRO_MONTHLYSTRIPE_PRICE_TEAM_MONTHLY
- Frontend
NEXT_PUBLIC_API_URL(e.g.http://localhost:8080)NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYNEXT_PUBLIC_ANALYTICS_PROVIDER(console,posthog, ornone)NEXT_PUBLIC_POSTHOG_KEYNEXT_PUBLIC_POSTHOG_HOSTNEXT_PUBLIC_SUPPORT_PROVIDER(crispornone)NEXT_PUBLIC_CRISP_WEBSITE_IDNEXT_PUBLIC_ERROR_REPORTING_PROVIDER(console,sentry, ornone)NEXT_PUBLIC_SENTRY_DSNNEXT_PUBLIC_SENTRY_ENVIRONMENT- Locale is stored in a
localecookie (supported:en,es)
SQL migrations live in backend/migrations/.
Apply them before using auth/billing endpoints.
Recommended: run the built-in migration CLI (tracks applied migrations in schema_migrations):
make migrate-upInitial migration files (applied in order):
backend/migrations/0001_identity_tenancy_billing.up.sqlbackend/migrations/0002_jobs_audit_files.up.sqlbackend/migrations/0003_personal_workspaces.up.sqlbackend/migrations/0004_team_owner_enforcement.up.sqlbackend/migrations/0005_org_invites.up.sql
Run infra first:
make infra-upThis starts Postgres, Redis, and a local OpenTelemetry collector (for local tracing).
Optional: run a local end-to-end smoke test (infra + api + worker + ui):
make smoke-localIf your local Node version can't run Next.js, skip the UI step:
make smoke-local SMOKE_ARGS=--skip-uiSmoke test uses a separate Postgres database (default saas_core_template_smoke) and recreates it each run. Override with SMOKE_DB_NAME=<name>.
Start backend in one terminal:
make dev-apiStart worker in another terminal (jobs + email):
make dev-workerStart frontend in another terminal:
make dev-uiOpen:
- Frontend:
http://localhost:3000 - Backend health:
http://localhost:8080/healthz - Backend readiness:
http://localhost:8080/readyz - Backend metadata:
http://localhost:8080/api/v1/meta - Sign in:
http://localhost:3000/sign-in - Pricing:
http://localhost:3000/pricing
Stop local infra:
make infra-downRun locally:
make ciGitHub Actions workflow runs on pull requests and pushes to main, develop, and dev:
- Backend:
go test,go vet, build - Frontend: install, lint, typecheck, build
CI currently runs on main, develop, and dev branches.
main: release branchdevelop: integration branchdevorfeature/*: feature implementation branches
Recommended flow:
- Build in
devorfeature/*. - Open PR into
develop. - Release from
developintomain.
- Template version source of truth:
VERSION - Versioning scheme: SemVer (
MAJOR.MINOR.PATCH) - CI validates
VERSIONformat onmain,develop, anddev.
See docs/operations/git-branching-and-versioning.md for full guidance.
This template deploys:
- Backend + Postgres on Render (see
render.yaml) - Frontend on Vercel (deploy
frontend/)
- Connect this GitHub repository in Render.
- Create services from the
render.yamlblueprint. - Set backend secrets:
REDIS_URL,CLERK_SECRET_KEY,STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET, Stripe price IDs. - Set
APP_BASE_URLto your Vercel frontend URL (used for Stripe return URLs). - Ensure auto-deploy is enabled for
main.
- Import the repo in Vercel.
- Set project root directory to
frontend/. - Set environment variables:
NEXT_PUBLIC_API_URL= Render backend URLNEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= Clerk publishable key
- Deploy.
Deployment flow:
- Push to
main-> GitHub Actions CI passes -> Render auto-deploys backend; Vercel deploys frontend.
- Local Redis exists for parity; production uses Upstash Redis.
mainbranch protection should require CI checks before merge.- Auth/billing provider boundaries and migration playbooks are documented in
docs/.
After cloning, run:
./scripts/init-template.sh "your-project-name"This replaces saas-core-template references across tracked files (including Go module/import paths and deployment service names) and prints follow-up commands.