[{"data":1,"prerenderedAt":656},["ShallowReactive",2],{"navigation_docs":3,"landing":64},[4,29,49],{"title":5,"path":6,"stem":7,"children":8,"page":28},"Getting Started","\u002Fdocs\u002Fgetting-started","docs\u002F1.getting-started",[9,14,19,24],{"title":10,"path":11,"stem":12,"icon":13},"Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":15,"path":16,"stem":17,"icon":18},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F1.getting-started\u002F2.installation","i-lucide-rocket",{"title":20,"path":21,"stem":22,"icon":23},"Configuration","\u002Fdocs\u002Fgetting-started\u002Fconfiguration","docs\u002F1.getting-started\u002F3.configuration","i-lucide-sliders-horizontal",{"title":25,"path":26,"stem":27,"icon":18},"Deployment","\u002Fdocs\u002Fgetting-started\u002Fdeployment","docs\u002F1.getting-started\u002F4.deployment",false,{"title":30,"path":31,"stem":32,"children":33,"page":28},"Architecture","\u002Fdocs\u002Farchitecture","docs\u002F2.architecture",[34,39,44],{"title":35,"path":36,"stem":37,"icon":38},"Layers","\u002Fdocs\u002Farchitecture\u002Flayers","docs\u002F2.architecture\u002F1.layers","i-lucide-layers",{"title":40,"path":41,"stem":42,"icon":43},"Multi-tenancy","\u002Fdocs\u002Farchitecture\u002Fmulti-tenancy","docs\u002F2.architecture\u002F2.multi-tenancy","i-lucide-building-2",{"title":45,"path":46,"stem":47,"icon":48},"Testing","\u002Fdocs\u002Farchitecture\u002Ftesting","docs\u002F2.architecture\u002F3.testing","i-lucide-flask-conical",{"title":50,"path":51,"stem":52,"children":53,"page":28},"Reference","\u002Fdocs\u002Freference","docs\u002F3.reference",[54,59],{"title":55,"path":56,"stem":57,"icon":58},"Commands","\u002Fdocs\u002Freference\u002Fcommands","docs\u002F3.reference\u002F1.commands","i-lucide-terminal",{"title":60,"path":61,"stem":62,"icon":63},"Decisions","\u002Fdocs\u002Freference\u002Fdecisions","docs\u002F3.reference\u002F2.decisions","i-lucide-git-branch",{"id":65,"title":66,"body":67,"description":66,"extension":648,"meta":649,"navigation":160,"path":650,"seo":651,"stem":654,"__hash__":655},"landing\u002Findex.md","",{"type":68,"value":69,"toc":646},"minimark",[70,218,397,411,510,606,612,633,642],[71,72,76,80,83,86,102],"u-page-hero",{"className":73,"orientation":75},[74],"grid-surface","horizontal",[77,78,79],"template",{"v-slot:headline":66},"Free · MIT · Nuxt 4 · Supabase · Postgres RLS",[77,81,82],{"v-slot:title":66},"Ship multi-tenant SaaS, not scaffolding.",[77,84,85],{"v-slot:description":66},"A free, MIT-licensed Nuxt 4 + Supabase starter where access control lives in the\ndatabase, every subsystem is wired but off by default, and one tenant can never\nread another's rows — proven by a test, not a promise.",[77,87,88,94],{"v-slot:links":66},[89,90,93],"u-button",{"to":16,"trailing-icon":91,"size":92},"i-lucide-arrow-right","xl","Get started",[89,95,101],{"to":96,"variant":97,"size":92,"color":98,"icon":99,"target":100},"https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fgstack","subtle","neutral","i-simple-icons-github","_blank","View on GitHub",[103,104,108],"pre",{"className":105,"code":106,"language":107,"meta":66,"style":66},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","gh repo create my-app --template TerrorSquad\u002Fgstack --private --clone\ncd my-app            # or hit \"Use this template\" on GitHub\n\npnpm install\npnpm setup          # pick your integrations; writes .env\npnpm supabase start # local Postgres + Auth (needs Docker)\npnpm db:reset       # migrate + seed a demo tenant\npnpm dev            # http:\u002F\u002Flocalhost:3000\n","bash",[109,110,111,142,155,162,171,182,196,207],"code",{"__ignoreMap":66},[112,113,116,120,124,127,130,133,136,139],"span",{"class":114,"line":115},"line",1,[112,117,119],{"class":118},"sBMFI","gh",[112,121,123],{"class":122},"sfazB"," repo",[112,125,126],{"class":122}," create",[112,128,129],{"class":122}," my-app",[112,131,132],{"class":122}," --template",[112,134,135],{"class":122}," TerrorSquad\u002Fgstack",[112,137,138],{"class":122}," --private",[112,140,141],{"class":122}," --clone\n",[112,143,145,149,151],{"class":114,"line":144},2,[112,146,148],{"class":147},"s2Zo4","cd",[112,150,129],{"class":122},[112,152,154],{"class":153},"sHwdD","            # or hit \"Use this template\" on GitHub\n",[112,156,158],{"class":114,"line":157},3,[112,159,161],{"emptyLinePlaceholder":160},true,"\n",[112,163,165,168],{"class":114,"line":164},4,[112,166,167],{"class":118},"pnpm",[112,169,170],{"class":122}," install\n",[112,172,174,176,179],{"class":114,"line":173},5,[112,175,167],{"class":118},[112,177,178],{"class":122}," setup",[112,180,181],{"class":153},"          # pick your integrations; writes .env\n",[112,183,185,187,190,193],{"class":114,"line":184},6,[112,186,167],{"class":118},[112,188,189],{"class":122}," supabase",[112,191,192],{"class":122}," start",[112,194,195],{"class":153}," # local Postgres + Auth (needs Docker)\n",[112,197,199,201,204],{"class":114,"line":198},7,[112,200,167],{"class":118},[112,202,203],{"class":122}," db:reset",[112,205,206],{"class":153},"       # migrate + seed a demo tenant\n",[112,208,210,212,215],{"class":114,"line":209},8,[112,211,167],{"class":118},[112,213,214],{"class":122}," dev",[112,216,217],{"class":153},"            # http:\u002F\u002Flocalhost:3000\n",[219,220,222,225,228,381,393],"u-page-section",{"headline":221},"01 — The guarantee",[77,223,224],{"v-slot:title":66},"The isolation test",[77,226,227],{"v-slot:description":66},"Most starters claim multi-tenancy. This one logs in as a second tenant and\nasserts the first tenant's rows are invisible — through the rendered page, the\nsearch box, and the SSR payload. Widen a policy by accident and this goes red.",[103,229,234],{"className":230,"code":231,"filename":232,"language":233,"meta":66,"style":66},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","test('a Globex admin cannot see Acme notes', async ({ page }) => {\n  await login(page, ADMIN2.email)\n\n  \u002F\u002F The seeded Acme secret title must be absent from Globex's notes list.\n  await page.goto('\u002Fnotes')\n  await expect(page.getByText(ACME_SECRET_NOTE_TITLE)).toHaveCount(0)\n})\n","e2e\u002Ftenant-isolation.spec.ts","ts",[109,235,236,277,306,310,315,337,374],{"__ignoreMap":66},[112,237,238,241,245,249,252,254,257,261,264,268,271,274],{"class":114,"line":115},[112,239,240],{"class":147},"test",[112,242,244],{"class":243},"sTEyZ","(",[112,246,248],{"class":247},"sMK4o","'",[112,250,251],{"class":122},"a Globex admin cannot see Acme notes",[112,253,248],{"class":247},[112,255,256],{"class":247},",",[112,258,260],{"class":259},"spNyl"," async",[112,262,263],{"class":247}," ({",[112,265,267],{"class":266},"sHdIc"," page",[112,269,270],{"class":247}," })",[112,272,273],{"class":259}," =>",[112,275,276],{"class":247}," {\n",[112,278,279,283,286,289,292,294,297,300,303],{"class":114,"line":144},[112,280,282],{"class":281},"s7zQu","  await",[112,284,285],{"class":147}," login",[112,287,244],{"class":288},"swJcz",[112,290,291],{"class":243},"page",[112,293,256],{"class":247},[112,295,296],{"class":243}," ADMIN2",[112,298,299],{"class":247},".",[112,301,302],{"class":243},"email",[112,304,305],{"class":288},")\n",[112,307,308],{"class":114,"line":157},[112,309,161],{"emptyLinePlaceholder":160},[112,311,312],{"class":114,"line":164},[112,313,314],{"class":153},"  \u002F\u002F The seeded Acme secret title must be absent from Globex's notes list.\n",[112,316,317,319,321,323,326,328,330,333,335],{"class":114,"line":173},[112,318,282],{"class":281},[112,320,267],{"class":243},[112,322,299],{"class":247},[112,324,325],{"class":147},"goto",[112,327,244],{"class":288},[112,329,248],{"class":247},[112,331,332],{"class":122},"\u002Fnotes",[112,334,248],{"class":247},[112,336,305],{"class":288},[112,338,339,341,344,346,348,350,353,355,358,361,363,366,368,372],{"class":114,"line":184},[112,340,282],{"class":281},[112,342,343],{"class":147}," expect",[112,345,244],{"class":288},[112,347,291],{"class":243},[112,349,299],{"class":247},[112,351,352],{"class":147},"getByText",[112,354,244],{"class":288},[112,356,357],{"class":243},"ACME_SECRET_NOTE_TITLE",[112,359,360],{"class":288},"))",[112,362,299],{"class":247},[112,364,365],{"class":147},"toHaveCount",[112,367,244],{"class":288},[112,369,371],{"class":370},"sbssI","0",[112,373,305],{"class":288},[112,375,376,379],{"class":114,"line":198},[112,377,378],{"class":247},"}",[112,380,305],{"class":243},[382,383,384,385,388,389,392],"p",{},"Every table carries a ",[109,386,387],{},"tenant_id",", and RLS scopes rows through a\n",[109,390,391],{},"current_tenant_id()"," security-definer helper that reads the caller's own\nprofile. Page-level role gates are UX only — a bug in a component cannot leak\nanother tenant's data, because the component never had the rows.",[89,394],{"to":41,"label":395,"trailing-icon":91,"variant":396},"How multi-tenancy works","link",[219,398,400,403,406],{"headline":399},"02 — What it looks like",[77,401,402],{"v-slot:title":66},"The screens you don't have to build",[77,404,405],{"v-slot:description":66},"Dashboard, notes CRUD, tenant admin with invites and impersonation, billing,\naccount with avatar upload, pricing, changelog and a feedback widget — all in\nthe template on the first run, in both themes and on mobile.",[89,407],{"to":408,"label":409,"trailing-icon":91,"size":410},"\u002Fscreens","See the screens","lg",[219,412,414,417,425,488,497,507],{"headline":413},"03 — Batteries included",[77,415,416],{"v-slot:title":66},"Every subsystem is one env flag",[77,418,419,420,424],{"v-slot:description":66},"Nothing half-works. Each subsystem is fully wired and switched off until you set\nits flag, so a bare clone runs end-to-end with ",[421,422,423],"strong",{},"zero third-party accounts"," —\nSupabase runs locally in Docker.",[426,427,428,444],"table",{},[429,430,431],"thead",{},[432,433,434,438,441],"tr",{},[435,436,437],"th",{},"Subsystem",[435,439,440],{},"Flag",[435,442,443],{},"Needs an account",[445,446,447,462,475],"tbody",{},[432,448,449,453,459],{},[450,451,452],"td",{},"Supabase — Postgres, Auth, RLS, Storage",[450,454,455],{},[456,457,458],"em",{},"core, always on",[450,460,461],{},"No (local via Docker)",[432,463,464,467,472],{},[450,465,466],{},"Feedback widget → your own DB",[450,468,469],{},[109,470,471],{},"NUXT_PUBLIC_FEEDBACK_ENABLED",[450,473,474],{},"No",[432,476,477,480,485],{},[450,478,479],{},"Billing — checkout, portal, webhooks",[450,481,482],{},[109,483,484],{},"NUXT_PUBLIC_BILLING_ENABLED",[450,486,487],{},"Polar",[382,489,490,491,299],{},"Six more — OAuth, notifications, analytics, the onboarding tour, error tracking\nand log forwarding — are on ",[89,492],{"to":493,"label":494,"variant":396,"className":495},"\u002Fstack","the stack page",[496],"!p-0",[382,498,499,502,503,506],{},[109,500,501],{},"pnpm setup"," writes the flags, ",[109,504,505],{},"pnpm doctor"," verifies them, and both read the\nsame manifest — so a subsystem can't be half-configured without one of them\nsaying so.",[89,508],{"to":21,"label":509,"trailing-icon":91,"variant":396},"Configuration reference",[219,511,513,516,519,603],{"headline":512},"04 — Proof, not adjectives",[77,514,515],{"v-slot:title":66},"What CI won't let you break",[77,517,518],{"v-slot:description":66},"The interesting part of a starter isn't what it ships on day one, it's what it\nrefuses to let you regress on day ninety.",[426,520,521,531],{},[429,522,523],{},[432,524,525,528],{},[435,526,527],{},"Gate",[435,529,530],{},"What it catches",[445,532,533,543,561,571,581,591],{},[432,534,535,540],{},[450,536,537],{},[109,538,539],{},"pnpm lint",[450,541,542],{},"oxlint + oxfmt, auto-fixing on pre-commit",[432,544,545,550],{},[450,546,547],{},[109,548,549],{},"pnpm lint:i18n",[450,551,552,553,556,557,560],{},"a key added to ",[109,554,555],{},"en"," but not ",[109,558,559],{},"sr",", or a key nothing uses",[432,562,563,568],{},[450,564,565],{},[109,566,567],{},"pnpm test",[450,569,570],{},"logic + the email-shell drift test: auth templates that no longer match their generator",[432,572,573,578],{},[450,574,575],{},[109,576,577],{},"pnpm typecheck",[450,579,580],{},"a query that under-selects, against types generated from the live schema",[432,582,583,588],{},[450,584,585],{},[109,586,587],{},"pnpm test:e2e",[450,589,590],{},"tenant isolation, auth flows, notes CRUD",[432,592,593,596],{},[450,594,595],{},"axe, twice daily",[450,597,598,599,602],{},"contrast and landmark failures, on every page, in ",[421,600,601],{},"both"," light and dark",[382,604,605],{},"Releases are cut by release-please from conventional commits; the user-facing\nchangelog is curated separately, so shipping a refactor doesn't spam your users.",[219,607],{"headline":608,":features":609,"description":610,"title":611},"05 — What else is in the box","[{\"title\":\"Type-safe end to end\",\"description\":\"Postgres schema → generated types → composables → UI. No ORM. Change a column and the build tells you every call site that cared.\",\"icon\":\"i-lucide-shield-check\"},{\"title\":\"Auth that survives SSR\",\"description\":\"Email\u002Fpassword plus GitHub and Google OAuth, password reset, confirmation, and a role-aware global middleware instead of the Supabase module's redirect.\",\"icon\":\"i-lucide-key-round\"},{\"title\":\"One email shell\",\"description\":\"Transactional 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.\",\"icon\":\"i-lucide-mail\"},{\"title\":\"Bilingual from the start\",\"description\":\"English and Serbian, with key parity enforced in CI. Dynamic keys are written so the usage checker can still resolve them.\",\"icon\":\"i-lucide-languages\"},{\"title\":\"Portable deploy\",\"description\":\"Nitro output, so Vercel is the default and not a lock-in. Supabase runs locally in Docker for development and CI alike.\",\"icon\":\"i-lucide-cloud\"},{\"title\":\"Decisions on the record\",\"description\":\"The reversible-but-significant choices — Polar over Stripe, no ORM yet, layers over a monorepo — are written down as ADRs with their trade-offs.\",\"icon\":\"i-lucide-file-text\"}]","Ten independent Nuxt layers over a shared design system, and the unglamorous work that usually gets deferred until it hurts.","The parts you would otherwise build twice",[219,613,615,618],{"headline":614},"06 — In the wild",[77,616,617],{"v-slot:title":66},"Someone built a real product on it in 13 days",[77,619,620,630],{"v-slot:description":66},[382,621,622,629],{},[623,624,628],"a",{"href":625,"rel":626},"https:\u002F\u002Fgoranninkovic.com\u002Fprojects\u002Fjob-finder",[627],"nofollow","job-finder"," is a multi-tenant\nproduct that reads company ATS boards, scores every posting against your CV with\nan LLM, and drafts the application material. Tenancy, auth, billing, email and\nthe admin surface came from GStack on day one, so the 13 days went on the part\nthat was actually the product.",[382,631,632],{},"That is the whole claim being made here: not that the starter is clever, but\nthat the work it removes is work you would otherwise do before writing a line\nof your own.",[219,634,636,639],{":links":635},"[{\"label\":\"Read the docs\",\"to\":\"\u002Fdocs\u002Fgetting-started\u002Fintroduction\",\"size\":\"xl\",\"trailingIcon\":\"i-lucide-arrow-right\"},{\"label\":\"Browse the decisions\",\"to\":\"\u002Fdocs\u002Freference\u002Fdecisions\",\"size\":\"xl\",\"color\":\"neutral\",\"variant\":\"subtle\"}]",[77,637,638],{"v-slot:title":66},"Free and MIT licensed",[77,640,641],{"v-slot:description":66},"Clone it, rename it with one command, and start building your product instead of\nits scaffolding.",[643,644,645],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":66,"searchDepth":144,"depth":144,"links":647},[],"md",{},"\u002F",{"title":652,"description":653},"GStack — RLS-first Nuxt + Supabase SaaS starter","An opinionated, type-safe, batteries-included stack for shipping multi-tenant SaaS with Nuxt. Auth, RLS multi-tenancy, billing, email, i18n, a11y and release automation, wired and flag-gated.","index","FWRk2m9lsQNEMOMYxkQMawmlZ_L38IOQYI2DNmw35rE",1785951587325]