[{"data":1,"prerenderedAt":171},["ShallowReactive",2],{"project:gstack":3},{"id":4,"title":5,"body":6,"demo":133,"description":134,"docs":135,"draft":136,"extension":137,"highlights":138,"image":148,"language":149,"meta":150,"navigation":151,"order":128,"path":152,"repo":153,"seo":154,"site":133,"stack":155,"stem":163,"tagline":164,"tags":165,"__hash__":170},"projects\u002Fprojects\u002Fgstack.md","GStack",{"type":7,"value":8,"toc":126},"minimark",[9,14,35,42,46,84,87,91],[10,11,13],"h2",{"id":12},"the-architectural-idea","The architectural idea",[15,16,17,18,22,23,26,27,30,31,34],"p",{},"Most SaaS starters put tenancy in the application layer: a ",[19,20,21],"code",{},"where tenant_id = ?","\nthat someone eventually forgets. GStack pushes it into Postgres. Every table is\ntenant-scoped by row-level security through a ",[19,24,25],{},"current_tenant_id()"," helper, so a\nmissing filter in application code is not a data leak. Roles (",[19,28,29],{},"member"," \u002F\n",[19,32,33],{},"admin",") are enforced the same way, with page metadata handling UX only.",[15,36,37,38],{},"That is the distinction I care about: ",[39,40,41],"strong",{},"the database is the gate, the UI is the\nconvenience.",[10,43,45],{"id":44},"feature-layers","Feature layers",[15,47,48,49,52,53,52,55,58,59,52,62,52,65,52,68,52,71,52,74,52,77,80,81,83],{},"The codebase is split into ten Nuxt layers - ",[19,50,51],{},"account",", ",[19,54,33],{},[19,56,57],{},"analytics",",\n",[19,60,61],{},"billing",[19,63,64],{},"email",[19,66,67],{},"feedback",[19,69,70],{},"marketing",[19,72,73],{},"notes",[19,75,76],{},"tour",[19,78,79],{},"ui",". Each owns\nits pages, composables, and types, and most can be switched off with an\nenvironment variable. ",[19,82,73],{}," is a deliberately boring CRUD example that shows\nthe pattern to copy.",[15,85,86],{},"Layers are not decoration here. They mean a feature can be deleted without\ngrepping the whole app, and a new domain can be added without touching existing\nones.",[10,88,90],{"id":89},"operational-maturity","Operational maturity",[92,93,94,105,111,117],"ul",{},[95,96,97,100,101,104],"li",{},[39,98,99],{},"Billing"," via Polar checkout, customer portal, and webhook, with plan gating\nthrough a ",[19,102,103],{},"useSubscription()"," composable",[95,106,107,110],{},[39,108,109],{},"Notifications"," - a database trigger fans an event out to tenant admins, a\nwebhook mirrors it to transactional email",[95,112,113,116],{},[39,114,115],{},"Observability"," - Sentry on client and server, PostHog analytics, and\nfeature flags that degrade safely when the service is unreachable",[95,118,119,122,123],{},[39,120,121],{},"CI"," - lint, typecheck, tests, plus dedicated accessibility and screenshot\nworkflows; screenshots in the README regenerate with ",[19,124,125],{},"pnpm screenshots",{"title":127,"searchDepth":128,"depth":128,"links":129},"",2,[130,131,132],{"id":12,"depth":128,"text":13},{"id":44,"depth":128,"text":45},{"id":89,"depth":128,"text":90},null,"A production-ready starting point for multi-tenant SaaS: Nuxt 4, Supabase, row-level-security tenancy, billing, notifications, and observability already wired together and tested.","https:\u002F\u002Fterrorsquad.github.io\u002Fgstack\u002F",false,"md",[139,142,145],{"value":140,"label":141},"10","isolated Nuxt layers, each a self-contained feature",{"value":143,"label":144},"RLS","tenancy enforced in the database, not app code",{"value":146,"label":147},"19","test files, plus automated a11y and screenshot CI","\u002Fprojects\u002Fgstack.webp","TypeScript",{},true,"\u002Fprojects\u002Fgstack","https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fgstack",{"title":5,"description":134},[156,149,157,158,159,160,161,162],"Nuxt 4","Supabase","PostgreSQL","Nuxt UI v4","Playwright","Polar","Resend","projects\u002Fgstack","Multi-tenant SaaS starter, batteries included",[166,167,168,169],"Architecture","SaaS","Multi-tenancy","Nuxt","CFRvvIXS0QVIUtEgtXfJ97uMOYAUnsPG8edFdRVlPxI",1785847686106]