Ship multi-tenant SaaS, not scaffolding.
gh repo create my-app --template TerrorSquad/gstack --private --clone
cd my-app # or hit "Use this template" on GitHub
pnpm install
pnpm setup # pick your integrations; writes .env
pnpm supabase start # local Postgres + Auth (needs Docker)
pnpm db:reset # migrate + seed a demo tenant
pnpm dev # http://localhost:3000
The isolation test
test('a Globex admin cannot see Acme notes', async ({ page }) => {
await login(page, ADMIN2.email)
// The seeded Acme secret title must be absent from Globex's notes list.
await page.goto('/notes')
await expect(page.getByText(ACME_SECRET_NOTE_TITLE)).toHaveCount(0)
})
Every table carries a tenant_id, and RLS scopes rows through a
current_tenant_id() security-definer helper that reads the caller's own
profile. Page-level role gates are UX only — a bug in a component cannot leak
another tenant's data, because the component never had the rows.
The screens you don't have to build
Every subsystem is one env flag
| Subsystem | Flag | Needs an account |
|---|---|---|
| Supabase — Postgres, Auth, RLS, Storage | core, always on | No (local via Docker) |
| Feedback widget → your own DB | NUXT_PUBLIC_FEEDBACK_ENABLED | No |
| Billing — checkout, portal, webhooks | NUXT_PUBLIC_BILLING_ENABLED | Polar |
Six more — OAuth, notifications, analytics, the onboarding tour, error tracking and log forwarding — are on the stack page.
pnpm setup writes the flags, pnpm doctor verifies them, and both read the
same manifest — so a subsystem can't be half-configured without one of them
saying so.
What CI won't let you break
| Gate | What it catches |
|---|---|
pnpm lint | oxlint + oxfmt, auto-fixing on pre-commit |
pnpm lint:i18n | a key added to en but not sr, or a key nothing uses |
pnpm test | logic + the email-shell drift test: auth templates that no longer match their generator |
pnpm typecheck | a query that under-selects, against types generated from the live schema |
pnpm test:e2e | tenant isolation, auth flows, notes CRUD |
| axe, twice daily | contrast and landmark failures, on every page, in both light and dark |
Releases are cut by release-please from conventional commits; the user-facing changelog is curated separately, so shipping a refactor doesn't spam your users.
The parts you would otherwise build twice
- Type-safe end to endPostgres schema → generated types → composables → UI. No ORM. Change a column and the build tells you every call site that cared.
- Auth that survives SSREmail/password plus GitHub and Google OAuth, password reset, confirmation, and a role-aware global middleware instead of the Supabase module's redirect.
- One email shellTransactional mail and the Supabase auth templates render from the same generated shell, so the branding can't drift. A unit test fails if it does.
- Bilingual from the startEnglish and Serbian, with key parity enforced in CI. Dynamic keys are written so the usage checker can still resolve them.
- Portable deployNitro output, so Vercel is the default and not a lock-in. Supabase runs locally in Docker for development and CI alike.
- Decisions on the recordThe reversible-but-significant choices — Polar over Stripe, no ORM yet, layers over a monorepo — are written down as ADRs with their trade-offs.
Someone built a real product on it in 13 days
job-finder is a multi-tenant product that reads company ATS boards, scores every posting against your CV with an LLM, and drafts the application material. Tenancy, auth, billing, email and the admin surface came from GStack on day one, so the 13 days went on the part that was actually the product.
That is the whole claim being made here: not that the starter is clever, but that the work it removes is work you would otherwise do before writing a line of your own.