[{"data":1,"prerenderedAt":17586},["ShallowReactive",2],{"navigation:en":3,"blog-list":311},[4],{"title":5,"path":6,"stem":7,"children":8,"icon":10},"Nuxfire Docs","\u002Fdocs","docs\u002F0.index",[9,11,152],{"title":5,"path":6,"stem":7,"icon":10},"i-lucide-layout-grid",{"title":12,"path":13,"stem":14,"children":15,"icon":151},"SaaS Starter Kit","\u002Fdocs\u002Fsaas-starter-kit","docs\u002F1.saas-starter-kit\u002F0.index",[16,19,63,129],{"title":17,"path":13,"stem":14,"icon":18},"What is the SaaS Starter Kit?","i-lucide-info",{"title":20,"icon":21,"hide":22,"path":23,"stem":24,"children":25,"page":62},"Getting Started","i-lucide-play",true,"\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started","docs\u002F1.saas-starter-kit\u002F1.getting-started",[26,30,34,38,42,46,50,54,58],{"title":27,"path":28,"stem":29},"Quick Start","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fquick-start","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F0.quick-start",{"title":31,"path":32,"stem":33},"Installation","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Finstallation","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F1.installation",{"title":35,"path":36,"stem":37},"Cloudflare Access","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fcloudflare-access","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F2.cloudflare-access",{"title":39,"path":40,"stem":41},"Configuration","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fconfiguration","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F3.configuration",{"title":43,"path":44,"stem":45},"Secrets","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fsecrets","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F4.secrets",{"title":47,"path":48,"stem":49},"Environment Variables","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fenvironment-variables","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F5.environment-variables",{"title":51,"path":52,"stem":53},"Deploy","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fdeploy","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F6.deploy",{"title":55,"path":56,"stem":57},"Local Development","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Flocal-development","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F7.local-development",{"title":59,"path":60,"stem":61},"Project Structure","\u002Fdocs\u002Fsaas-starter-kit\u002Fgetting-started\u002Fproject-structure","docs\u002F1.saas-starter-kit\u002F1.getting-started\u002F8.project-structure",false,{"title":64,"icon":65,"hide":22,"path":66,"stem":67,"children":68,"page":62},"Features","i-lucide-box","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures","docs\u002F1.saas-starter-kit\u002F2.features",[69,73,77,81,85,89,93,97,101,105,109,113,117,121,125],{"title":70,"path":71,"stem":72},"Authentication","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fauth","docs\u002F1.saas-starter-kit\u002F2.features\u002F1.auth",{"title":74,"path":75,"stem":76},"Rate Limiting","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frate-limiting","docs\u002F1.saas-starter-kit\u002F2.features\u002F10.rate-limiting",{"title":78,"path":79,"stem":80},"WebSockets","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fwebsockets","docs\u002F1.saas-starter-kit\u002F2.features\u002F11.websockets",{"title":82,"path":83,"stem":84},"Blog","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fblog","docs\u002F1.saas-starter-kit\u002F2.features\u002F14.blog",{"title":86,"path":87,"stem":88},"Documentation","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdocumentation","docs\u002F1.saas-starter-kit\u002F2.features\u002F15.documentation",{"title":90,"path":91,"stem":92},"Analytics","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fanalytics","docs\u002F1.saas-starter-kit\u002F2.features\u002F16.analytics",{"title":94,"path":95,"stem":96},"Platform Admin Console","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fplatform-admin","docs\u002F1.saas-starter-kit\u002F2.features\u002F17.platform-admin",{"title":98,"path":99,"stem":100},"Database","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fdatabase","docs\u002F1.saas-starter-kit\u002F2.features\u002F2.database",{"title":102,"path":103,"stem":104},"Payments & Billing","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fpayments","docs\u002F1.saas-starter-kit\u002F2.features\u002F3.payments",{"title":106,"path":107,"stem":108},"Emails","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Femails","docs\u002F1.saas-starter-kit\u002F2.features\u002F4.emails",{"title":110,"path":111,"stem":112},"Notifications","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fnotifications","docs\u002F1.saas-starter-kit\u002F2.features\u002F5.notifications",{"title":114,"path":115,"stem":116},"Role-Based Access Control","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Frbac","docs\u002F1.saas-starter-kit\u002F2.features\u002F6.rbac",{"title":118,"path":119,"stem":120},"Async Jobs","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fasync-jobs","docs\u002F1.saas-starter-kit\u002F2.features\u002F7.async-jobs",{"title":122,"path":123,"stem":124},"Storage","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Fstorage","docs\u002F1.saas-starter-kit\u002F2.features\u002F8.storage",{"title":126,"path":127,"stem":128},"Internationalization","\u002Fdocs\u002Fsaas-starter-kit\u002Ffeatures\u002Finternationalization","docs\u002F1.saas-starter-kit\u002F2.features\u002F9.internationalization",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":62},"Concepts","i-lucide-graduation-cap","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts","docs\u002F1.saas-starter-kit\u002F3.concepts",[135,139,143,147],{"title":136,"path":137,"stem":138},"Technology Stack","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Ftech-stack","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F0.tech-stack",{"title":140,"path":141,"stem":142},"SST.dev","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fsst","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F1.sst",{"title":144,"path":145,"stem":146},"NuxtHub","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fnuxt-hub","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F2.nuxt-hub",{"title":148,"path":149,"stem":150},"Design System & Branding","\u002Fdocs\u002Fsaas-starter-kit\u002Fconcepts\u002Fdesign-system","docs\u002F1.saas-starter-kit\u002F3.concepts\u002F3.design-system","i-lucide-zap",{"title":153,"path":154,"stem":155,"children":156,"icon":159},"Fireskills","\u002Fdocs\u002Ffireskills","docs\u002F2.fireskills\u002F0.index",[157,160,219,249,275,293],{"title":158,"path":154,"stem":155,"icon":159},"What is Fireskills?","i-lucide-flame",{"title":20,"icon":21,"hide":22,"path":161,"stem":162,"children":163,"page":62},"\u002Fdocs\u002Ffireskills\u002Fgetting-started","docs\u002F2.fireskills\u002F1.getting-started",[164,167,171,175,179,183,187,191,195,199,203,207,211,215],{"title":27,"path":165,"stem":166},"\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fquick-start","docs\u002F2.fireskills\u002F1.getting-started\u002F00.quick-start",{"title":168,"path":169,"stem":170},"Workflow & Governance","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fworkflow","docs\u002F2.fireskills\u002F1.getting-started\u002F01.workflow",{"title":172,"path":173,"stem":174},"Project Context","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fproject-context","docs\u002F2.fireskills\u002F1.getting-started\u002F02.project-context",{"title":176,"path":177,"stem":178},"Step 0: Setup","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-setup","docs\u002F2.fireskills\u002F1.getting-started\u002F03.step-setup",{"title":180,"path":181,"stem":182},"Step 1: Inbox","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-inbox","docs\u002F2.fireskills\u002F1.getting-started\u002F04.step-inbox",{"title":184,"path":185,"stem":186},"Step 2: Backlog","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-backlog","docs\u002F2.fireskills\u002F1.getting-started\u002F05.step-backlog",{"title":188,"path":189,"stem":190},"Step 3: Specify","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-specify","docs\u002F2.fireskills\u002F1.getting-started\u002F06.step-specify",{"title":192,"path":193,"stem":194},"Step 4: Validate","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-validate","docs\u002F2.fireskills\u002F1.getting-started\u002F07.step-validate",{"title":196,"path":197,"stem":198},"Step 5: Tasks","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tasks","docs\u002F2.fireskills\u002F1.getting-started\u002F08.step-tasks",{"title":200,"path":201,"stem":202},"Step 6: TDD\u002FBDD","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-tdd-bdd","docs\u002F2.fireskills\u002F1.getting-started\u002F09.step-tdd-bdd",{"title":204,"path":205,"stem":206},"Step 7: Implement","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-implement","docs\u002F2.fireskills\u002F1.getting-started\u002F10.step-implement",{"title":208,"path":209,"stem":210},"Step 8: Documentator","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fstep-documentator","docs\u002F2.fireskills\u002F1.getting-started\u002F11.step-documentator",{"title":212,"path":213,"stem":214},"Late Changes","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Flate-changes","docs\u002F2.fireskills\u002F1.getting-started\u002F12.late-changes",{"title":216,"path":217,"stem":218},"Other Entry Points","\u002Fdocs\u002Ffireskills\u002Fgetting-started\u002Fother-entry-points","docs\u002F2.fireskills\u002F1.getting-started\u002F13.other-entry-points",{"title":220,"icon":221,"hide":22,"path":222,"stem":223,"children":224,"page":62},"Methodology","i-lucide-git-branch","\u002Fdocs\u002Ffireskills\u002Fmethodology","docs\u002F2.fireskills\u002F2.methodology",[225,229,233,237,241,245],{"title":226,"path":227,"stem":228},"The Three Acts","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-acts","docs\u002F2.fireskills\u002F2.methodology\u002F0.three-acts",{"title":230,"path":231,"stem":232},"The Three Gates","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fthree-gates","docs\u002F2.fireskills\u002F2.methodology\u002F1.three-gates",{"title":234,"path":235,"stem":236},"The Canonical Spec","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fcanonical-spec","docs\u002F2.fireskills\u002F2.methodology\u002F2.canonical-spec",{"title":238,"path":239,"stem":240},"Lifecycle States","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Flifecycle-states","docs\u002F2.fireskills\u002F2.methodology\u002F3.lifecycle-states",{"title":242,"path":243,"stem":244},"Effort Scale","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Feffort-scale","docs\u002F2.fireskills\u002F2.methodology\u002F4.effort-scale",{"title":246,"path":247,"stem":248},"Milestones & Governance","\u002Fdocs\u002Ffireskills\u002Fmethodology\u002Fgovernance-and-milestones","docs\u002F2.fireskills\u002F2.methodology\u002F5.governance-and-milestones",{"title":250,"icon":251,"hide":22,"path":252,"stem":253,"children":254,"page":62},"Skills","i-lucide-layers","\u002Fdocs\u002Ffireskills\u002Fskills","docs\u002F2.fireskills\u002F3.skills",[255,259,263,267,271],{"title":256,"path":257,"stem":258},"Catalog","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fcatalog","docs\u002F2.fireskills\u002F3.skills\u002F0.catalog",{"title":260,"path":261,"stem":262},"Act I: Define","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-1-define","docs\u002F2.fireskills\u002F3.skills\u002F1.act-1-define",{"title":264,"path":265,"stem":266},"Act II: Design & Prove","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-2-design-prove","docs\u002F2.fireskills\u002F3.skills\u002F2.act-2-design-prove",{"title":268,"path":269,"stem":270},"Act III: Deliver","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fact-3-deliver","docs\u002F2.fireskills\u002F3.skills\u002F3.act-3-deliver",{"title":272,"path":273,"stem":274},"Governance & Decisions","\u002Fdocs\u002Ffireskills\u002Fskills\u002Fgovernance-and-decisions","docs\u002F2.fireskills\u002F3.skills\u002F4.governance-and-decisions",{"title":276,"icon":277,"hide":22,"path":278,"stem":279,"children":280,"page":62},"Specialists","i-lucide-sparkles","\u002Fdocs\u002Ffireskills\u002Fspecialists","docs\u002F2.fireskills\u002F4.specialists",[281,285,289],{"title":282,"path":283,"stem":284},"Overview","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Foverview","docs\u002F2.fireskills\u002F4.specialists\u002F0.overview",{"title":286,"path":287,"stem":288},"Nuxfire Stack","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fnuxfire-stack","docs\u002F2.fireskills\u002F4.specialists\u002F1.nuxfire-stack",{"title":290,"path":291,"stem":292},"Engineering Specialists","\u002Fdocs\u002Ffireskills\u002Fspecialists\u002Fengineering-specialists","docs\u002F2.fireskills\u002F4.specialists\u002F2.engineering-specialists",{"title":294,"icon":295,"hide":22,"path":296,"stem":297,"children":298,"page":62},"CLI & Tooling","i-lucide-terminal","\u002Fdocs\u002Ffireskills\u002Fcli","docs\u002F2.fireskills\u002F5.cli",[299,303,307],{"title":300,"path":301,"stem":302},"CLI Overview","\u002Fdocs\u002Ffireskills\u002Fcli\u002Foverview","docs\u002F2.fireskills\u002F5.cli\u002F0.overview",{"title":304,"path":305,"stem":306},"Commands","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fcommands","docs\u002F2.fireskills\u002F5.cli\u002F1.commands",{"title":308,"path":309,"stem":310},"Safety Scripts","\u002Fdocs\u002Ffireskills\u002Fcli\u002Fgovernance-scripts","docs\u002F2.fireskills\u002F5.cli\u002F2.governance-scripts",[312,1991,3436,4879,5419,5870,6348,6954,7375,7925,8474,8891,9675,10128,11102,12005,12908,13599,14219,14910,15451,15960,16470,17028],{"id":313,"title":314,"author":315,"authorType":316,"body":317,"category":1968,"date":1969,"description":1970,"extension":1971,"image":1972,"lang":1973,"meta":1974,"navigation":22,"ogImage":1975,"path":1976,"readTime":1977,"seo":1978,"stem":1979,"tags":1980,"translations":1986,"updated":316,"__hash__":1990},"blog\u002Fblog\u002Fgit-branches-stage-production-primeiro-lancamento.md","Git para o Primeiro Lançamento: Branches stage e production, do Repositório Vazio ao Ar","Equipe Nuxfire",null,{"type":318,"value":319,"toc":1948},"minimark",[320,334,337,342,352,355,406,419,423,458,462,541,553,558,564,568,590,596,602,678,700,722,728,732,738,744,762,789,796,816,822,841,845,851,857,863,901,907,924,928,934,943,956,970,989,999,1017,1036,1039,1058,1067,1071,1077,1081,1091,1095,1101,1144,1150,1178,1200,1204,1210,1250,1260,1270,1290,1315,1319,1325,1329,1335,1357,1368,1384,1390,1436,1442,1475,1478,1524,1533,1537,1543,1547,1556,1582,1591,1633,1640,1644,1650,1654,1660,1707,1717,1721,1727,1731,1850,1854,1934,1944],[321,322,323,324,328,329,333],"p",{},"Você extraiu o código-fonte do Nuxfire e está prestes a publicar seu primeiro produto. Antes de rodar qualquer comando ",[325,326,327],"code",{},"sst",", dedique vinte minutos ao que decide se os próximos cem deploys serão tranquilos ou estressantes: ",[330,331,332],"strong",{},"como as suas branches do Git se relacionam com o que está no ar",".",[321,335,336],{},"Este manual constrói um modelo pequeno e rigoroso. Todo comando Git aqui foi executado de ponta a ponta em um repositório de teste antes da publicação; as opções do GitHub CLI vêm do manual oficial. Cada etapa traz os comandos manuais e um prompt curto para colar em um agente de IA na sua IDE (Cursor, Claude Code, Copilot e similares).",[338,339,341],"h2",{"id":340},"o-modelo-em-um-minuto","O modelo em um minuto",[343,344,350],"pre",{"className":345,"code":347,"language":348,"meta":349},[346],"language-text","feature\u002Fx ──▶ stage ──(validated)──▶ production\n              default branch          moves ONLY by fast-forward\n              you work here           = what you approved for live\n","text","",[325,351,347],{"__ignoreMap":349},[321,353,354],{},"Duas branches de longa duração e cinco regras:",[356,357,358,368,380,388,396],"ol",{},[359,360,361,367],"li",{},[330,362,363,364,333],{},"Todo o trabalho acontece na ",[325,365,366],{},"stage"," Ela é a branch padrão, então pull requests e clones caem nela.",[359,369,370,379],{},[330,371,372,373,376,377,333],{},"A ",[325,374,375],{},"production"," só avança por fast-forward a partir da ",[325,378,366],{}," Sem commits de merge e sem commits diretos.",[359,381,382,387],{},[330,383,384,385,333],{},"Nunca faça force-push na ",[325,386,375],{}," Se um push for rejeitado, é a rede de segurança funcionando.",[359,389,390,395],{},[330,391,392,393,333],{},"Faça o deploy de produção a partir da branch ",[325,394,375],{}," Um deploy publica o que estiver em checkout na sua máquina, então a branch em que você está é a verdade.",[359,397,398,401,402,405],{},[330,399,400],{},"Marque com uma tag cada release que foi ao ar"," (",[325,403,404],{},"v1.0.0","). A branch diz \"aprovado\", a tag diz \"confirmado no ar\".",[321,407,408,409,412,413,415,416,418],{},"Por que não manter a ",[325,410,411],{},"main"," também? Uma terceira branch de longa duração é uma terceira resposta para \"qual é o código mais recente?\". O Nuxfire já chama seus estágios de deploy de ",[325,414,366],{}," e ",[325,417,375],{},", então as branches espelham esses nomes e não há nada para traduzir na sua cabeça.",[338,420,422],{"id":421},"o-que-você-precisa","O que você precisa",[424,425,426,440,447],"ul",{},[359,427,428,429,432,433,436,437,333],{},"Git ",[330,430,431],{},"2.28 ou mais novo"," (para o ",[325,434,435],{},"git init -b","). Confira com ",[325,438,439],{},"git --version",[359,441,442,443,446],{},"Uma conta no GitHub (pessoal ou organização). O GitHub CLI (",[325,444,445],{},"gh",") é opcional; cada etapa também mostra o caminho pela interface web.",[359,448,449,450,453,454,333],{},"A pasta do Nuxfire extraída e o ",[325,451,452],{},"bun install"," feito, como em ",[455,456,457],"a",{"href":32},"Instalação",[338,459,461],{"id":460},"etapa-1-diga-ao-git-quem-você-é","Etapa 1: diga ao Git quem você é",[343,463,467],{"className":464,"code":465,"language":466,"meta":349,"style":349},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","git --version\ngit config --global user.name \"Your Name\"\ngit config --global user.email \"you@example.com\"\ngit config --global init.defaultBranch stage\n","bash",[325,468,469,482,507,526],{"__ignoreMap":349},[470,471,474,478],"span",{"class":472,"line":473},"line",1,[470,475,477],{"class":476},"sR7ES","git",[470,479,481],{"class":480},"sFhLe"," --version\n",[470,483,485,487,491,494,497,501,504],{"class":472,"line":484},2,[470,486,477],{"class":476},[470,488,490],{"class":489},"sLACW"," config",[470,492,493],{"class":480}," --global",[470,495,496],{"class":489}," user.name",[470,498,500],{"class":499},"siCPE"," \"",[470,502,503],{"class":489},"Your Name",[470,505,506],{"class":499},"\"\n",[470,508,510,512,514,516,519,521,524],{"class":472,"line":509},3,[470,511,477],{"class":476},[470,513,490],{"class":489},[470,515,493],{"class":480},[470,517,518],{"class":489}," user.email",[470,520,500],{"class":499},[470,522,523],{"class":489},"you@example.com",[470,525,506],{"class":499},[470,527,529,531,533,535,538],{"class":472,"line":528},4,[470,530,477],{"class":476},[470,532,490],{"class":489},[470,534,493],{"class":480},[470,536,537],{"class":489}," init.defaultBranch",[470,539,540],{"class":489}," stage\n",[321,542,543,544,546,547,550,551,333],{},"A última linha faz todo repositório novo que você criar começar na ",[325,545,366],{},", em vez de ",[325,548,549],{},"master"," ou ",[325,552,411],{},[321,554,555],{},[330,556,557],{},"Prompt para agentes de IA:",[343,559,562],{"className":560,"code":561,"language":348,"meta":349},[346],"Check `git --version` (2.28 or newer is required). Show my global git\nuser.name and user.email. If either is empty, ask me for the values\nbefore setting them. Do not change anything else.\n",[325,563,561],{"__ignoreMap":349},[338,565,567],{"id":566},"etapa-2-proteja-seus-segredos-e-faça-o-primeiro-commit","Etapa 2: proteja seus segredos e faça o primeiro commit",[321,569,570,571,574,575,415,578,581,582,585,586,589],{},"Sua pasta já contém arquivos que ",[330,572,573],{},"nunca"," podem chegar ao Git: ",[325,576,577],{},".env.production",[325,579,580],{},".env.stage"," guardam credenciais reais (a verificação prévia do deploy até confere se os valores do banco estão preenchidos), e ",[325,583,584],{},".sst"," é o estado local do deploy. O ",[325,587,588],{},".gitignore"," do Nuxfire cobre todos eles:",[343,591,594],{"className":592,"code":593,"language":348,"meta":349},[346],".env\n.env.*\n!.env.example\n.sst\n",[325,595,593],{"__ignoreMap":349},[321,597,598,599,601],{},"Crie o repositório na ",[325,600,366],{},", prove que as regras funcionam e só então adicione tudo:",[343,603,605],{"className":464,"code":604,"language":466,"meta":349,"style":349},"git init -b stage\ngit check-ignore -v .env.production .env.stage .sst\ngit add -A\ngit diff --cached --name-only | grep -E \"(^|\u002F)\\.env\"\n",[325,606,607,619,638,648],{"__ignoreMap":349},[470,608,609,611,614,617],{"class":472,"line":473},[470,610,477],{"class":476},[470,612,613],{"class":489}," init",[470,615,616],{"class":480}," -b",[470,618,540],{"class":489},[470,620,621,623,626,629,632,635],{"class":472,"line":484},[470,622,477],{"class":476},[470,624,625],{"class":489}," check-ignore",[470,627,628],{"class":480}," -v",[470,630,631],{"class":489}," .env.production",[470,633,634],{"class":489}," .env.stage",[470,636,637],{"class":489}," .sst\n",[470,639,640,642,645],{"class":472,"line":509},[470,641,477],{"class":476},[470,643,644],{"class":489}," add",[470,646,647],{"class":480}," -A\n",[470,649,650,652,655,658,661,665,668,671,673,676],{"class":472,"line":528},[470,651,477],{"class":476},[470,653,654],{"class":489}," diff",[470,656,657],{"class":480}," --cached",[470,659,660],{"class":480}," --name-only",[470,662,664],{"class":663},"sGXK2"," |",[470,666,667],{"class":476}," grep",[470,669,670],{"class":480}," -E",[470,672,500],{"class":499},[470,674,675],{"class":489},"(^|\u002F)\\.env",[470,677,506],{"class":499},[321,679,680,681,684,685,688,689,692,693,696,697,699],{},"O ",[325,682,683],{},"check-ignore"," precisa imprimir a regra que casa com cada arquivo. O último comando precisa imprimir ",[330,686,687],{},"somente"," ",[325,690,691],{},".env.example",". Se qualquer outro arquivo ",[325,694,695],{},".env*"," aparecer, pare e corrija o ",[325,698,588],{}," antes de commitar.",[343,701,703],{"className":464,"code":702,"language":466,"meta":349,"style":349},"git commit -m \"chore: initial commit from Nuxfire\"\n",[325,704,705],{"__ignoreMap":349},[470,706,707,709,712,715,717,720],{"class":472,"line":473},[470,708,477],{"class":476},[470,710,711],{"class":489}," commit",[470,713,714],{"class":480}," -m",[470,716,500],{"class":499},[470,718,719],{"class":489},"chore: initial commit from Nuxfire",[470,721,506],{"class":499},[723,724,725],"blockquote",{},[321,726,727],{},"Se um segredo for commitado alguma vez, apagar o arquivo não basta: ele continua no histórico. Trate a credencial como comprometida, troque-a primeiro e só depois limpe o histórico.",[321,729,730],{},[330,731,557],{},[343,733,736],{"className":734,"code":735,"language":348,"meta":349},[346],"In the project root, run `git init -b stage` only if there is no .git\nfolder. With `git check-ignore -v`, verify that .env.production,\n.env.stage and .sst are ignored and that .env.example is not. Run\n`git add -A` and print `git diff --cached --name-only`. STOP and warn me\nif any .env* file other than .env.example is listed. If it is clean,\ncommit with the message \"chore: initial commit from Nuxfire\".\n",[325,737,735],{"__ignoreMap":349},[338,739,741,742],{"id":740},"etapa-3-crie-o-repositório-no-github-e-envie-a-stage","Etapa 3: crie o repositório no GitHub e envie a ",[325,743,366],{},[321,745,746,747,750,751,753,754,757,758,761],{},"Crie um repositório ",[330,748,749],{},"privado e vazio",": sem README, sem ",[325,752,588],{},", sem licença. No navegador, use ",[330,755,756],{},"New repository",", escolha ",[330,759,760],{},"Private"," e deixe as opções de inicialização desmarcadas. Ou, pela CLI, dentro da pasta do projeto:",[343,763,765],{"className":464,"code":764,"language":466,"meta":349,"style":349},"gh repo create my-product --private --source=. --remote=origin\n",[325,766,767],{"__ignoreMap":349},[470,768,769,771,774,777,780,783,786],{"class":472,"line":473},[470,770,445],{"class":476},[470,772,773],{"class":489}," repo",[470,775,776],{"class":489}," create",[470,778,779],{"class":489}," my-product",[470,781,782],{"class":480}," --private",[470,784,785],{"class":480}," --source=.",[470,787,788],{"class":480}," --remote=origin\n",[321,790,791,792,795],{},"A CLI adiciona o remoto ",[325,793,794],{},"origin"," para você. Sem ela, adicione o remoto manualmente, com a URL que o GitHub mostra:",[343,797,799],{"className":464,"code":798,"language":466,"meta":349,"style":349},"git remote add origin git@github.com:YOUR-USER\u002Fmy-product.git\n",[325,800,801],{"__ignoreMap":349},[470,802,803,805,808,810,813],{"class":472,"line":473},[470,804,477],{"class":476},[470,806,807],{"class":489}," remote",[470,809,644],{"class":489},[470,811,812],{"class":489}," origin",[470,814,815],{"class":489}," git@github.com:YOUR-USER\u002Fmy-product.git\n",[321,817,818,819,821],{},"Nos dois casos, publique a ",[325,820,366],{}," e configure o rastreamento do remoto:",[343,823,825],{"className":464,"code":824,"language":466,"meta":349,"style":349},"git push -u origin stage\n",[325,826,827],{"__ignoreMap":349},[470,828,829,831,834,837,839],{"class":472,"line":473},[470,830,477],{"class":476},[470,832,833],{"class":489}," push",[470,835,836],{"class":480}," -u",[470,838,812],{"class":489},[470,840,540],{"class":489},[321,842,843],{},[330,844,557],{},[343,846,849],{"className":847,"code":848,"language":348,"meta":349},[346],"Create a PRIVATE, empty GitHub repository named \u003Cname> under \u003Cowner>\n(no README, no .gitignore, no license), add it as `origin`, and push the\n`stage` branch with upstream tracking. Use `gh` if it is installed;\notherwise print the exact commands for me to run. Never use --force.\n",[325,850,848],{"__ignoreMap":349},[338,852,854,855],{"id":853},"etapa-4-crie-a-production","Etapa 4: crie a ",[325,856,375],{},[321,858,859,860,862],{},"Crie a branch a partir da ",[325,861,366],{},", no mesmo commit, publique-a e volte:",[343,864,866],{"className":464,"code":865,"language":466,"meta":349,"style":349},"git switch -c production\ngit push -u origin production\ngit switch stage\n",[325,867,868,881,893],{"__ignoreMap":349},[470,869,870,872,875,878],{"class":472,"line":473},[470,871,477],{"class":476},[470,873,874],{"class":489}," switch",[470,876,877],{"class":480}," -c",[470,879,880],{"class":489}," production\n",[470,882,883,885,887,889,891],{"class":472,"line":484},[470,884,477],{"class":476},[470,886,833],{"class":489},[470,888,836],{"class":480},[470,890,812],{"class":489},[470,892,880],{"class":489},[470,894,895,897,899],{"class":472,"line":509},[470,896,477],{"class":476},[470,898,874],{"class":489},[470,900,540],{"class":489},[321,902,903,904,906],{},"Agora adicione uma trava local para que a ",[325,905,375],{}," só possa avançar por fast-forward. O Git vai recusar qualquer merge que não seja um fast-forward:",[343,908,910],{"className":464,"code":909,"language":466,"meta":349,"style":349},"git config branch.production.mergeOptions --ff-only\n",[325,911,912],{"__ignoreMap":349},[470,913,914,916,918,921],{"class":472,"line":473},[470,915,477],{"class":476},[470,917,490],{"class":489},[470,919,920],{"class":489}," branch.production.mergeOptions",[470,922,923],{"class":480}," --ff-only\n",[321,925,926],{},[330,927,557],{},[343,929,932],{"className":930,"code":931,"language":348,"meta":349},[346],"Create branch `production` from `stage`, push it with upstream tracking,\nswitch back to `stage`, then run\n`git config branch.production.mergeOptions --ff-only`.\nConfirm the result with `git branch -vv`.\n",[325,933,931],{"__ignoreMap":349},[338,935,937,938,940,941],{"id":936},"etapa-5-torne-a-stage-a-branch-padrão-e-remova-a-main","Etapa 5: torne a ",[325,939,366],{}," a branch padrão e remova a ",[325,942,411],{},[321,944,945,946,948,949,952,953,955],{},"Quem decide a branch padrão é o GitHub, não o seu Git local. Mesmo em um repositório vazio ela pode continuar sendo a ",[325,947,411],{},", então confira explicitamente. O GitHub exige ",[330,950,951],{},"mais de uma branch"," para trocar a padrão, e é por isso que a ",[325,954,375],{}," precisava existir antes.",[321,957,958,959,962,963,965,966,969],{},"No navegador: ",[330,960,961],{},"Settings → Branches → Default branch",", clique no ícone de troca, escolha ",[325,964,366],{},", ",[330,967,968],{},"Update"," e confirme. Pela CLI:",[343,971,973],{"className":464,"code":972,"language":466,"meta":349,"style":349},"gh repo edit --default-branch stage\n",[325,974,975],{"__ignoreMap":349},[470,976,977,979,981,984,987],{"class":472,"line":473},[470,978,445],{"class":476},[470,980,773],{"class":489},[470,982,983],{"class":489}," edit",[470,985,986],{"class":480}," --default-branch",[470,988,540],{"class":489},[321,990,991,992,994,995,998],{},"Depois veja o que existe no remoto e, se a ",[325,993,411],{}," estiver lá (por exemplo, porque o repositório foi criado com um README), apague-a. Faça isso ",[330,996,997],{},"depois"," de trocar a padrão, nunca antes:",[343,1000,1002],{"className":464,"code":1001,"language":466,"meta":349,"style":349},"git ls-remote --heads origin\n",[325,1003,1004],{"__ignoreMap":349},[470,1005,1006,1008,1011,1014],{"class":472,"line":473},[470,1007,477],{"class":476},[470,1009,1010],{"class":489}," ls-remote",[470,1012,1013],{"class":480}," --heads",[470,1015,1016],{"class":489}," origin\n",[343,1018,1020],{"className":464,"code":1019,"language":466,"meta":349,"style":349},"git push origin --delete main\n",[325,1021,1022],{"__ignoreMap":349},[470,1023,1024,1026,1028,1030,1033],{"class":472,"line":473},[470,1025,477],{"class":476},[470,1027,833],{"class":489},[470,1029,812],{"class":489},[470,1031,1032],{"class":480}," --delete",[470,1034,1035],{"class":489}," main\n",[321,1037,1038],{},"Por fim, atualize o seu ponteiro local para a branch padrão do remoto:",[343,1040,1042],{"className":464,"code":1041,"language":466,"meta":349,"style":349},"git remote set-head origin --auto\n",[325,1043,1044],{"__ignoreMap":349},[470,1045,1046,1048,1050,1053,1055],{"class":472,"line":473},[470,1047,477],{"class":476},[470,1049,807],{"class":489},[470,1051,1052],{"class":489}," set-head",[470,1054,812],{"class":489},[470,1056,1057],{"class":480}," --auto\n",[321,1059,1060,1061,1063,1064,1066],{},"Se a ",[325,1062,411],{}," foi criada com um README, ela tem um histórico sem relação com o seu. Não faça merge dela na ",[325,1065,366],{},"; apenas apague.",[321,1068,1069],{},[330,1070,557],{},[343,1072,1075],{"className":1073,"code":1074,"language":348,"meta":349},[346],"Check which branch is the GitHub default with\n`gh repo view --json defaultBranchRef`. If it is not `stage`, run\n`gh repo edit --default-branch stage`. Then run\n`git ls-remote --heads origin`. If `main` exists, tell me and WAIT for my\nconfirmation before running `git push origin --delete main`. Finish with\n`git remote set-head origin --auto`.\n",[325,1076,1074],{"__ignoreMap":349},[338,1078,1080],{"id":1079},"etapa-6-travas-no-github-se-o-seu-plano-tiver","Etapa 6: travas no GitHub (se o seu plano tiver)",[321,1082,1083,1084,1087,1088,1090],{},"A trava local ",[325,1085,1086],{},"--ff-only"," não custa nada e não depende de plano. No GitHub, a proteção de branches e os rulesets dependem do seu plano e da visibilidade do repositório (os rulesets estão documentados para os planos GitHub Team e GitHub Enterprise). Se o seu plano oferecer, proteja a ",[325,1089,375],{}," bloqueando force pushes e restringindo exclusões. Se não, o processo e a trava local bastam, desde que você nunca faça force-push.",[338,1092,1094],{"id":1093},"etapa-7-o-dia-a-dia","Etapa 7: o dia a dia",[321,1096,1097,1098,1100],{},"Trabalhando sozinho, faça commit na ",[325,1099,366],{}," e envie:",[343,1102,1104],{"className":464,"code":1103,"language":466,"meta":349,"style":349},"git switch stage\ngit add -A\ngit commit -m \"feat: describe the change\"\ngit push\n",[325,1105,1106,1114,1122,1137],{"__ignoreMap":349},[470,1107,1108,1110,1112],{"class":472,"line":473},[470,1109,477],{"class":476},[470,1111,874],{"class":489},[470,1113,540],{"class":489},[470,1115,1116,1118,1120],{"class":472,"line":484},[470,1117,477],{"class":476},[470,1119,644],{"class":489},[470,1121,647],{"class":480},[470,1123,1124,1126,1128,1130,1132,1135],{"class":472,"line":509},[470,1125,477],{"class":476},[470,1127,711],{"class":489},[470,1129,714],{"class":480},[470,1131,500],{"class":499},[470,1133,1134],{"class":489},"feat: describe the change",[470,1136,506],{"class":499},[470,1138,1139,1141],{"class":472,"line":528},[470,1140,477],{"class":476},[470,1142,1143],{"class":489}," push\n",[321,1145,1146,1147,1149],{},"Trabalhando em equipe, use branches curtas e pull requests para a ",[325,1148,366],{},":",[343,1151,1153],{"className":464,"code":1152,"language":466,"meta":349,"style":349},"git switch -c feat\u002Fbilling-copy\ngit push -u origin feat\u002Fbilling-copy\n",[325,1154,1155,1166],{"__ignoreMap":349},[470,1156,1157,1159,1161,1163],{"class":472,"line":473},[470,1158,477],{"class":476},[470,1160,874],{"class":489},[470,1162,877],{"class":480},[470,1164,1165],{"class":489}," feat\u002Fbilling-copy\n",[470,1167,1168,1170,1172,1174,1176],{"class":472,"line":484},[470,1169,477],{"class":476},[470,1171,833],{"class":489},[470,1173,836],{"class":480},[470,1175,812],{"class":489},[470,1177,1165],{"class":489},[321,1179,1180,1181,401,1187,965,1190,965,1193,965,1196,1199],{},"Use ",[455,1182,1186],{"href":1183,"rel":1184},"https:\u002F\u002Fwww.conventionalcommits.org\u002F",[1185],"nofollow","Conventional Commits",[325,1188,1189],{},"feat:",[325,1191,1192],{},"fix:",[325,1194,1195],{},"docs:",[325,1197,1198],{},"chore:","). O próprio histórico do Nuxfire os segue, o que deixa as notas de release quase de graça.",[338,1201,1203],{"id":1202},"etapa-8-o-portão-antes-do-primeiro-lançamento","Etapa 8: o portão antes do primeiro lançamento",[321,1205,1206,1207,1209],{},"Nada chega à ",[325,1208,375],{}," sem passar pelas mesmas verificações, sempre:",[343,1211,1213],{"className":464,"code":1212,"language":466,"meta":349,"style":349},"bun install\nbun run test\nbun run doctor -- --stage production\n",[325,1214,1215,1223,1233],{"__ignoreMap":349},[470,1216,1217,1220],{"class":472,"line":473},[470,1218,1219],{"class":476},"bun",[470,1221,1222],{"class":489}," install\n",[470,1224,1225,1227,1230],{"class":472,"line":484},[470,1226,1219],{"class":476},[470,1228,1229],{"class":489}," run",[470,1231,1232],{"class":489}," test\n",[470,1234,1235,1237,1239,1242,1245,1248],{"class":472,"line":509},[470,1236,1219],{"class":476},[470,1238,1229],{"class":489},[470,1240,1241],{"class":489}," doctor",[470,1243,1244],{"class":480}," --",[470,1246,1247],{"class":480}," --stage",[470,1249,880],{"class":489},[321,1251,680,1252,1255,1256,1259],{},[325,1253,1254],{},"doctor"," é a verificação prévia do deploy: valida seu token da Cloudflare, seus valores ",[325,1257,1258],{},"BRAND_*",", os segredos do banco e a zona do domínio, e diz a correção exata de cada problema antes que você gaste minutos em um deploy que vai falhar.",[321,1261,1262,1263,1266,1267,1149],{},"Para um primeiro produto, faça o deploy de um ",[330,1264,1265],{},"estágio de validação"," antes da produção. Um estágio que não é de produção ganha seus próprios Workers, conexão de banco e segredos, e responde em ",[325,1268,1269],{},"\u003Cstage>.dev.\u003Cseu-domínio>",[343,1271,1273],{"className":464,"code":1272,"language":466,"meta":349,"style":349},"bun run deploy -- --stage stage\n",[325,1274,1275],{"__ignoreMap":349},[470,1276,1277,1279,1281,1284,1286,1288],{"class":472,"line":473},[470,1278,1219],{"class":476},[470,1280,1229],{"class":489},[470,1282,1283],{"class":489}," deploy",[470,1285,1244],{"class":480},[470,1287,1247],{"class":480},[470,1289,540],{"class":489},[321,1291,680,1292,1295,1296,965,1299,415,1301,1303,1304,688,1307,1310,1311,1314],{},[325,1293,1294],{},"--"," é necessário para que a flag chegue ao script. O token e os segredos estão em ",[455,1297,1298],{"href":36},"Acesso à Cloudflare",[455,1300,43],{"href":44},[455,1302,51],{"href":52},". Um pré-requisito só da produção vale conhecer desde já: o domínio raiz e o ",[325,1305,1306],{},"www",[330,1308,1309],{},"não"," podem ter nenhum registro A, AAAA ou CNAME no seu DNS, senão a Cloudflare rejeita o domínio personalizado (erro ",[325,1312,1313],{},"100117",").",[321,1316,1317],{},[330,1318,557],{},[343,1320,1323],{"className":1321,"code":1322,"language":348,"meta":349},[346],"Run `bun install`, `bun run test` and\n`bun run doctor -- --stage production`. Report every failure with its\nexact message and the fix. Do NOT deploy anything.\n",[325,1324,1322],{"__ignoreMap":349},[338,1326,1328],{"id":1327},"etapa-9-o-primeiro-lançamento","Etapa 9: o primeiro lançamento",[321,1330,1331,1332,1334],{},"Tudo está verde na ",[325,1333,366],{},". Promova-a:",[343,1336,1338],{"className":464,"code":1337,"language":466,"meta":349,"style":349},"git status\ngit push origin stage\n",[325,1339,1340,1347],{"__ignoreMap":349},[470,1341,1342,1344],{"class":472,"line":473},[470,1343,477],{"class":476},[470,1345,1346],{"class":489}," status\n",[470,1348,1349,1351,1353,1355],{"class":472,"line":484},[470,1350,477],{"class":476},[470,1352,833],{"class":489},[470,1354,812],{"class":489},[470,1356,540],{"class":489},[321,1358,680,1359,1362,1363,1365,1366,1149],{},[325,1360,1361],{},"git status"," precisa dizer que a árvore de trabalho está limpa. Agora avance a ",[325,1364,375],{}," por fast-forward para exatamente o mesmo commit, sem sair da ",[325,1367,366],{},[343,1369,1371],{"className":464,"code":1370,"language":466,"meta":349,"style":349},"git push origin stage:production\n",[325,1372,1373],{"__ignoreMap":349},[470,1374,1375,1377,1379,1381],{"class":472,"line":473},[470,1376,477],{"class":476},[470,1378,833],{"class":489},[470,1380,812],{"class":489},[470,1382,1383],{"class":489}," stage:production\n",[321,1385,1386,1387,1389],{},"Esse push só é aceito se for um fast-forward puro. Confirme que as duas branches são idênticas e mude para a ",[325,1388,375],{},", porque é dela que você faz o deploy:",[343,1391,1393],{"className":464,"code":1392,"language":466,"meta":349,"style":349},"git fetch origin\ngit diff --stat stage origin\u002Fproduction\ngit switch production\ngit pull --ff-only\n",[325,1394,1395,1404,1419,1427],{"__ignoreMap":349},[470,1396,1397,1399,1402],{"class":472,"line":473},[470,1398,477],{"class":476},[470,1400,1401],{"class":489}," fetch",[470,1403,1016],{"class":489},[470,1405,1406,1408,1410,1413,1416],{"class":472,"line":484},[470,1407,477],{"class":476},[470,1409,654],{"class":489},[470,1411,1412],{"class":480}," --stat",[470,1414,1415],{"class":489}," stage",[470,1417,1418],{"class":489}," origin\u002Fproduction\n",[470,1420,1421,1423,1425],{"class":472,"line":509},[470,1422,477],{"class":476},[470,1424,874],{"class":489},[470,1426,880],{"class":489},[470,1428,1429,1431,1434],{"class":472,"line":528},[470,1430,477],{"class":476},[470,1432,1433],{"class":489}," pull",[470,1435,923],{"class":480},[321,1437,680,1438,1441],{},[325,1439,1440],{},"diff"," precisa imprimir nada. Rode a verificação prévia mais uma vez e faça o deploy:",[343,1443,1445],{"className":464,"code":1444,"language":466,"meta":349,"style":349},"bun run doctor -- --stage production\nbun run deploy -- --stage production\n",[325,1446,1447,1461],{"__ignoreMap":349},[470,1448,1449,1451,1453,1455,1457,1459],{"class":472,"line":473},[470,1450,1219],{"class":476},[470,1452,1229],{"class":489},[470,1454,1241],{"class":489},[470,1456,1244],{"class":480},[470,1458,1247],{"class":480},[470,1460,880],{"class":489},[470,1462,1463,1465,1467,1469,1471,1473],{"class":472,"line":484},[470,1464,1219],{"class":476},[470,1466,1229],{"class":489},[470,1468,1283],{"class":489},[470,1470,1244],{"class":480},[470,1472,1247],{"class":480},[470,1474,880],{"class":489},[321,1476,1477],{},"Só depois que o deploy der certo, marque a release com uma tag e volte a trabalhar:",[343,1479,1481],{"className":464,"code":1480,"language":466,"meta":349,"style":349},"git tag -a v1.0.0 -m \"First production release\"\ngit push origin v1.0.0\ngit switch stage\n",[325,1482,1483,1505,1516],{"__ignoreMap":349},[470,1484,1485,1487,1490,1493,1496,1498,1500,1503],{"class":472,"line":473},[470,1486,477],{"class":476},[470,1488,1489],{"class":489}," tag",[470,1491,1492],{"class":480}," -a",[470,1494,1495],{"class":489}," v1.0.0",[470,1497,714],{"class":480},[470,1499,500],{"class":499},[470,1501,1502],{"class":489},"First production release",[470,1504,506],{"class":499},[470,1506,1507,1509,1511,1513],{"class":472,"line":484},[470,1508,477],{"class":476},[470,1510,833],{"class":489},[470,1512,812],{"class":489},[470,1514,1515],{"class":489}," v1.0.0\n",[470,1517,1518,1520,1522],{"class":472,"line":509},[470,1519,477],{"class":476},[470,1521,874],{"class":489},[470,1523,540],{"class":489},[321,1525,1526,1527,1529,1530,1532],{},"Se o deploy falhar, não edite a ",[325,1528,375],{},". Corrija adiante na ",[325,1531,366],{},", rode o portão de novo e repita o fast-forward. A branch pode ficar brevemente à frente do que está no ar, e a ausência da tag mostra exatamente isso.",[321,1534,1535],{},[330,1536,557],{},[343,1538,1541],{"className":1539,"code":1540,"language":348,"meta":349},[346],"Release checklist for the first release. Stop at the first failure:\n1. `git status` must be clean and on `stage`.\n2. `git push origin stage`.\n3. `git push origin stage:production`. If it is rejected, STOP and show\n   me `git log --oneline stage..production`. Never force.\n4. `git switch production && git pull --ff-only`.\n5. `bun run doctor -- --stage production`.\n6. Ask me for explicit confirmation, then run\n   `bun run deploy -- --stage production`.\n7. Only after a successful deploy: `git tag -a v1.0.0 -m \"First\n   production release\"` and `git push origin v1.0.0`.\n8. `git switch stage`.\n",[325,1542,1540],{"__ignoreMap":349},[338,1544,1546],{"id":1545},"etapa-10-impeça-as-duas-branches-de-se-afastarem","Etapa 10: impeça as duas branches de se afastarem",[321,1548,1549,1550,1552,1553,1555],{},"O modo de falha que pega todo mundo uma vez: uma correção cai na ",[325,1551,375],{}," (ou um deploy sai da ",[325,1554,366],{},") e as branches deixam de descrever o mesmo código sem ninguém notar. A verificação é uma linha, e deve sempre imprimir nada:",[343,1557,1559],{"className":464,"code":1558,"language":466,"meta":349,"style":349},"git fetch origin\ngit log --oneline stage..origin\u002Fproduction\n",[325,1560,1561,1569],{"__ignoreMap":349},[470,1562,1563,1565,1567],{"class":472,"line":473},[470,1564,477],{"class":476},[470,1566,1401],{"class":489},[470,1568,1016],{"class":489},[470,1570,1571,1573,1576,1579],{"class":472,"line":484},[470,1572,477],{"class":476},[470,1574,1575],{"class":489}," log",[470,1577,1578],{"class":480}," --oneline",[470,1580,1581],{"class":489}," stage..origin\u002Fproduction\n",[321,1583,1584,1585,1587,1588,1590],{},"Se imprimir commits, a ",[325,1586,375],{}," tem trabalho que a ",[325,1589,366],{}," não tem. Uma promoção será rejeitada, e isso está correto. Traga o trabalho de volta, publique e avance de novo:",[343,1592,1594],{"className":464,"code":1593,"language":466,"meta":349,"style":349},"git switch stage\ngit merge origin\u002Fproduction\ngit push origin stage\ngit push origin stage:production\n",[325,1595,1596,1604,1613,1623],{"__ignoreMap":349},[470,1597,1598,1600,1602],{"class":472,"line":473},[470,1599,477],{"class":476},[470,1601,874],{"class":489},[470,1603,540],{"class":489},[470,1605,1606,1608,1611],{"class":472,"line":484},[470,1607,477],{"class":476},[470,1609,1610],{"class":489}," merge",[470,1612,1418],{"class":489},[470,1614,1615,1617,1619,1621],{"class":472,"line":509},[470,1616,477],{"class":476},[470,1618,833],{"class":489},[470,1620,812],{"class":489},[470,1622,540],{"class":489},[470,1624,1625,1627,1629,1631],{"class":472,"line":528},[470,1626,477],{"class":476},[470,1628,833],{"class":489},[470,1630,812],{"class":489},[470,1632,1383],{"class":489},[321,1634,1635,1636,1639],{},"Se o merge relatar conflitos, resolva-os, rode o portão de novo e commite antes de enviar. Nunca resolva isso com ",[325,1637,1638],{},"--force",": você apagaria justamente os commits que quer preservar.",[321,1641,1642],{},[330,1643,557],{},[343,1645,1648],{"className":1646,"code":1647,"language":348,"meta":349},[346],"Run `git fetch origin`, then `git log --oneline stage..origin\u002Fproduction`.\nIf it prints anything: merge origin\u002Fproduction into stage, resolve any\nconflicts together with me, run `bun run test`, push stage, then\nfast-forward production with `git push origin stage:production`.\nNever use --force.\n",[325,1649,1647],{"__ignoreMap":349},[338,1651,1653],{"id":1652},"voltando-atrás","Voltando atrás",[321,1655,1656,1657,1659],{},"Não reescreva a ",[325,1658,375],{},". Faça o deploy do código de uma tag anterior:",[343,1661,1663],{"className":464,"code":1662,"language":466,"meta":349,"style":349},"git tag --sort=-creatordate\ngit switch --detach v1.0.0\nbun run deploy -- --stage production\ngit switch stage\n",[325,1664,1665,1674,1685,1699],{"__ignoreMap":349},[470,1666,1667,1669,1671],{"class":472,"line":473},[470,1668,477],{"class":476},[470,1670,1489],{"class":489},[470,1672,1673],{"class":480}," --sort=-creatordate\n",[470,1675,1676,1678,1680,1683],{"class":472,"line":484},[470,1677,477],{"class":476},[470,1679,874],{"class":489},[470,1681,1682],{"class":480}," --detach",[470,1684,1515],{"class":489},[470,1686,1687,1689,1691,1693,1695,1697],{"class":472,"line":509},[470,1688,1219],{"class":476},[470,1690,1229],{"class":489},[470,1692,1283],{"class":489},[470,1694,1244],{"class":480},[470,1696,1247],{"class":480},[470,1698,880],{"class":489},[470,1700,1701,1703,1705],{"class":472,"line":528},[470,1702,477],{"class":476},[470,1704,874],{"class":489},[470,1706,540],{"class":489},[321,1708,1709,1710,1713,1714,1716],{},"Isso restaura o ",[330,1711,1712],{},"código",". As migrações do banco são aplicadas a cada deploy e não são desfeitas, então planeje mudanças de schema primeiro como aditivas (adicione uma coluna, publique o código que a usa, remova a antiga depois). Em seguida, corrija adiante na ",[325,1715,366],{}," e promova uma nova release.",[321,1718,1719],{},[330,1720,557],{},[343,1722,1725],{"className":1723,"code":1724,"language":348,"meta":349},[346],"Do NOT touch the `production` branch. Show me\n`git tag --sort=-creatordate | head`, ask which tag to restore, run\n`git switch --detach \u003Ctag>`, and ask for my explicit confirmation before\n`bun run deploy -- --stage production`. Afterwards run `git switch stage`.\n",[325,1726,1724],{"__ignoreMap":349},[338,1728,1730],{"id":1729},"quando-algo-dá-errado","Quando algo dá errado",[1732,1733,1734,1750],"table",{},[1735,1736,1737],"thead",{},[1738,1739,1740,1744,1747],"tr",{},[1741,1742,1743],"th",{},"O que você vê",[1741,1745,1746],{},"O que significa",[1741,1748,1749],{},"O que fazer",[1751,1752,1753,1772,1787,1803,1822,1836],"tbody",{},[1738,1754,1755,1761,1769],{},[1756,1757,1758],"td",{},[325,1759,1760],{},"[rejected] stage -> production (non-fast-forward)",[1756,1762,372,1763,1765,1766,1768],{},[325,1764,375],{}," tem commits que a ",[325,1767,366],{}," não tem",[1756,1770,1771],{},"Etapa 10. Nunca use force.",[1738,1773,1774,1779,1784],{},[1756,1775,1776],{},[325,1777,1778],{},"fatal: Not possible to fast-forward, aborting.",[1756,1780,1781,1782],{},"A trava local impediu um merge na ",[325,1783,375],{},[1756,1785,1786],{},"Mesma causa e mesma solução acima",[1738,1788,1789,1794,1797],{},[1756,1790,1791,1792],{},"O GitHub não deixa apagar a ",[325,1793,411],{},[1756,1795,1796],{},"Em geral, ela ainda é a branch padrão",[1756,1798,1799,1800,1802],{},"Troque a padrão para ",[325,1801,366],{}," primeiro (Etapa 5)",[1738,1804,1805,1814,1819],{},[1756,1806,1807,1808,1810,1811],{},"Um arquivo ",[325,1809,695],{}," aparece em ",[325,1812,1813],{},"git diff --cached",[1756,1815,1816,1817],{},"Falta uma regra no ",[325,1818,588],{},[1756,1820,1821],{},"Corrija antes do commit, nunca depois",[1738,1823,1824,1827,1830],{},[1756,1825,1826],{},"Você fez o deploy da branch errada",[1756,1828,1829],{},"O deploy publica a árvore em checkout",[1756,1831,1832,1835],{},[325,1833,1834],{},"git branch --show-current"," antes de todo deploy",[1738,1837,1838,1844,1847],{},[1756,1839,1840,1841,1843],{},"Erro de domínio ",[325,1842,1313],{}," no primeiro deploy de produção",[1756,1845,1846],{},"O hostname já tem um registro DNS",[1756,1848,1849],{},"Apague o registro A, AAAA ou CNAME e faça o deploy de novo",[338,1851,1853],{"id":1852},"checklist","Checklist",[424,1855,1858,1872,1884,1895,1907,1919,1928],{"className":1856},[1857],"contains-task-list",[359,1859,1862,1866,1867,415,1869,1871],{"className":1860},[1861],"task-list-item",[1863,1864],"input",{"disabled":22,"type":1865},"checkbox"," O repositório é privado e só existem ",[325,1868,366],{},[325,1870,375],{}," no remoto",[359,1873,1875,1877,1878,1880,1881,1883],{"className":1874},[1861],[1863,1876],{"disabled":22,"type":1865}," A ",[325,1879,366],{}," é a branch padrão e a ",[325,1882,411],{}," não existe mais",[359,1885,1887,688,1889,1892,1893],{"className":1886},[1861],[1863,1888],{"disabled":22,"type":1865},[325,1890,1891],{},"git config branch.production.mergeOptions"," imprime ",[325,1894,1086],{},[359,1896,1898,1900,1901,1903,1904,1906],{"className":1897},[1861],[1863,1899],{"disabled":22,"type":1865}," Nenhum arquivo ",[325,1902,695],{},", exceto ",[325,1905,691],{},", está versionado",[359,1908,1910,688,1912,415,1915,1918],{"className":1909},[1861],[1863,1911],{"disabled":22,"type":1865},[325,1913,1914],{},"bun run test",[325,1916,1917],{},"bun run doctor -- --stage production"," estão verdes",[359,1920,1922,688,1924,1927],{"className":1921},[1861],[1863,1923],{"disabled":22,"type":1865},[325,1925,1926],{},"git diff --stat stage origin\u002Fproduction"," imprime nada antes do deploy",[359,1929,1931,1933],{"className":1930},[1861],[1863,1932],{"disabled":22,"type":1865}," A release só foi marcada com tag depois que o deploy deu certo",[321,1935,1936,1937,1939,1940,415,1942,333],{},"De volta à configuração: siga para ",[455,1938,1298],{"href":36}," para criar o token de API, depois ",[455,1941,43],{"href":44},[455,1943,51],{"href":52},[1945,1946,1947],"style",{},"html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sFhLe, html code.shiki .sFhLe{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}",{"title":349,"searchDepth":484,"depth":484,"links":1949},[1950,1951,1952,1953,1954,1956,1958,1960,1961,1962,1963,1964,1965,1966,1967],{"id":340,"depth":484,"text":341},{"id":421,"depth":484,"text":422},{"id":460,"depth":484,"text":461},{"id":566,"depth":484,"text":567},{"id":740,"depth":484,"text":1955},"Etapa 3: crie o repositório no GitHub e envie a stage",{"id":853,"depth":484,"text":1957},"Etapa 4: crie a production",{"id":936,"depth":484,"text":1959},"Etapa 5: torne a stage a branch padrão e remova a main",{"id":1079,"depth":484,"text":1080},{"id":1093,"depth":484,"text":1094},{"id":1202,"depth":484,"text":1203},{"id":1327,"depth":484,"text":1328},{"id":1545,"depth":484,"text":1546},{"id":1652,"depth":484,"text":1653},{"id":1729,"depth":484,"text":1730},{"id":1852,"depth":484,"text":1853},"DevOps","2026-09-25","Um manual de nível sênior para quem acabou de adquirir o Nuxfire: crie o repositório, torne a stage a branch padrão, remova a main, promova para production por fast-forward e publique sua primeira versão com segurança.","md","\u002Fimages\u002Fblog\u002Fgit-branching-stage-production.svg","pt-br",{},"\u002Fimages\u002Fblog\u002Fgit-branching-stage-production.png","\u002Fblog\u002Fgit-branches-stage-production-primeiro-lancamento","14 min",{"title":314,"description":1970},"blog\u002Fgit-branches-stage-production-primeiro-lancamento",[1981,1982,1983,1984,51,1985],"Git","GitHub","Branching","Release","SST",{"en":1987,"pt-br":1988,"es":1989},"git-branching-stage-production-first-release","git-branches-stage-production-primeiro-lancamento","git-ramas-stage-production-primer-lanzamiento","D9JQpAP_iVq5OXP1v3ulPn14KXjRyp-5w_8__oAJClg",{"id":1992,"title":1993,"author":1994,"authorType":316,"body":1995,"category":1968,"date":1969,"description":3427,"extension":1971,"image":1972,"lang":3428,"meta":3429,"navigation":22,"ogImage":1975,"path":3430,"readTime":1977,"seo":3431,"stem":3432,"tags":3433,"translations":3434,"updated":316,"__hash__":3435},"blog\u002Fblog\u002Fgit-branching-stage-production-first-release.md","Git for Your First Release: stage and production Branches, from Empty Repository to Live","Nuxfire Team",{"type":318,"value":1996,"toc":3407},[1997,2006,2009,2013,2018,2021,2066,2078,2082,2109,2113,2167,2178,2183,2188,2192,2210,2215,2221,2279,2297,2315,2320,2324,2329,2335,2351,2371,2377,2393,2399,2415,2419,2424,2430,2436,2470,2476,2490,2494,2499,2508,2521,2532,2548,2558,2572,2588,2591,2607,2616,2620,2625,2629,2638,2642,2648,2688,2693,2719,2733,2737,2743,2775,2783,2793,2811,2832,2836,2841,2845,2851,2871,2881,2895,2901,2941,2946,2978,2981,3021,3030,3034,3039,3043,3052,3074,3083,3123,3129,3133,3138,3142,3148,3192,3201,3205,3210,3214,3322,3324,3395,3405],[321,1998,1999,2000,2002,2003,333],{},"You extracted the Nuxfire source and you are about to deploy your first product. Before you run a single ",[325,2001,327],{}," command, spend twenty minutes on the thing that decides whether the next hundred deploys are boring or stressful: ",[330,2004,2005],{},"how your Git branches map to what is live",[321,2007,2008],{},"This manual builds a small, strict model. Every Git command in it was run end to end in a scratch repository before publishing; the GitHub CLI flags come from its official manual. Each step has the manual commands and a short prompt you can paste into an AI agent in your IDE (Cursor, Claude Code, Copilot and similar).",[338,2010,2012],{"id":2011},"the-model-in-one-minute","The model in one minute",[343,2014,2016],{"className":2015,"code":347,"language":348,"meta":349},[346],[325,2017,347],{"__ignoreMap":349},[321,2019,2020],{},"Two long-lived branches, five rules:",[356,2022,2023,2031,2041,2049,2058],{},[359,2024,2025,2030],{},[330,2026,2027,2028,333],{},"All work happens on ",[325,2029,366],{}," It is the default branch, so pull requests and clones land there.",[359,2032,2033,2040],{},[330,2034,2035,2037,2038,333],{},[325,2036,375],{}," only moves by fast-forward from ",[325,2039,366],{}," No merge commits, no direct commits.",[359,2042,2043,2048],{},[330,2044,2045,2046,333],{},"Never force-push ",[325,2047,375],{}," If a push is rejected, that is the safety net working.",[359,2050,2051,2057],{},[330,2052,2053,2054,2056],{},"Deploy production from the ",[325,2055,375],{}," branch."," A deploy ships whatever is checked out on your machine, so the branch you are on is the truth.",[359,2059,2060,401,2063,2065],{},[330,2061,2062],{},"Tag every release that went live",[325,2064,404],{},"). The branch says \"approved\", the tag says \"confirmed live\".",[321,2067,2068,2069,2071,2072,2074,2075,2077],{},"Why not keep ",[325,2070,411],{}," too? A third long-lived branch is a third answer to \"what is the latest code?\". Nuxfire already names its deployment stages ",[325,2073,366],{}," and ",[325,2076,375],{},", so the branch names mirror them and there is nothing to translate in your head.",[338,2079,2081],{"id":2080},"what-you-need","What you need",[424,2083,2084,2095,2101],{},[359,2085,428,2086,2089,2090,2092,2093,333],{},[330,2087,2088],{},"2.28 or newer"," (for ",[325,2091,435],{},"). Check with ",[325,2094,439],{},[359,2096,2097,2098,2100],{},"A GitHub account (personal or organization). The GitHub CLI (",[325,2099,445],{},") is optional; every step also shows the web-UI path.",[359,2102,2103,2104,2106,2107,333],{},"The Nuxfire folder extracted and ",[325,2105,452],{}," done, as in ",[455,2108,31],{"href":32},[338,2110,2112],{"id":2111},"step-1-tell-git-who-you-are","Step 1: tell Git who you are",[343,2114,2115],{"className":464,"code":465,"language":466,"meta":349,"style":349},[325,2116,2117,2123,2139,2155],{"__ignoreMap":349},[470,2118,2119,2121],{"class":472,"line":473},[470,2120,477],{"class":476},[470,2122,481],{"class":480},[470,2124,2125,2127,2129,2131,2133,2135,2137],{"class":472,"line":484},[470,2126,477],{"class":476},[470,2128,490],{"class":489},[470,2130,493],{"class":480},[470,2132,496],{"class":489},[470,2134,500],{"class":499},[470,2136,503],{"class":489},[470,2138,506],{"class":499},[470,2140,2141,2143,2145,2147,2149,2151,2153],{"class":472,"line":509},[470,2142,477],{"class":476},[470,2144,490],{"class":489},[470,2146,493],{"class":480},[470,2148,518],{"class":489},[470,2150,500],{"class":499},[470,2152,523],{"class":489},[470,2154,506],{"class":499},[470,2156,2157,2159,2161,2163,2165],{"class":472,"line":528},[470,2158,477],{"class":476},[470,2160,490],{"class":489},[470,2162,493],{"class":480},[470,2164,537],{"class":489},[470,2166,540],{"class":489},[321,2168,2169,2170,2172,2173,2175,2176,333],{},"The last line makes every new repository you create start on ",[325,2171,366],{}," instead of ",[325,2174,549],{}," or ",[325,2177,411],{},[321,2179,2180],{},[330,2181,2182],{},"Prompt for AI agents:",[343,2184,2186],{"className":2185,"code":561,"language":348,"meta":349},[346],[325,2187,561],{"__ignoreMap":349},[338,2189,2191],{"id":2190},"step-2-protect-your-secrets-then-make-the-first-commit","Step 2: protect your secrets, then make the first commit",[321,2193,2194,2195,2198,2199,2074,2201,2203,2204,2206,2207,2209],{},"Your folder already contains files that must ",[330,2196,2197],{},"never"," reach Git: ",[325,2200,577],{},[325,2202,580],{}," hold real credentials (the deploy preflight even checks that the database values are filled in), and ",[325,2205,584],{}," is local deploy state. Nuxfire's ",[325,2208,588],{}," covers them:",[343,2211,2213],{"className":2212,"code":593,"language":348,"meta":349},[346],[325,2214,593],{"__ignoreMap":349},[321,2216,2217,2218,2220],{},"Create the repository on ",[325,2219,366],{},", prove the rules work, and only then stage everything:",[343,2222,2223],{"className":464,"code":604,"language":466,"meta":349,"style":349},[325,2224,2225,2235,2249,2257],{"__ignoreMap":349},[470,2226,2227,2229,2231,2233],{"class":472,"line":473},[470,2228,477],{"class":476},[470,2230,613],{"class":489},[470,2232,616],{"class":480},[470,2234,540],{"class":489},[470,2236,2237,2239,2241,2243,2245,2247],{"class":472,"line":484},[470,2238,477],{"class":476},[470,2240,625],{"class":489},[470,2242,628],{"class":480},[470,2244,631],{"class":489},[470,2246,634],{"class":489},[470,2248,637],{"class":489},[470,2250,2251,2253,2255],{"class":472,"line":509},[470,2252,477],{"class":476},[470,2254,644],{"class":489},[470,2256,647],{"class":480},[470,2258,2259,2261,2263,2265,2267,2269,2271,2273,2275,2277],{"class":472,"line":528},[470,2260,477],{"class":476},[470,2262,654],{"class":489},[470,2264,657],{"class":480},[470,2266,660],{"class":480},[470,2268,664],{"class":663},[470,2270,667],{"class":476},[470,2272,670],{"class":480},[470,2274,500],{"class":499},[470,2276,675],{"class":489},[470,2278,506],{"class":499},[321,2280,2281,2282,2284,2285,688,2288,2290,2291,2293,2294,2296],{},"The ",[325,2283,683],{}," command must print the rule that matches each file. The last command must print ",[330,2286,2287],{},"only",[325,2289,691],{},". If any other ",[325,2292,695],{}," file shows up, stop and fix ",[325,2295,588],{}," before committing.",[343,2298,2299],{"className":464,"code":702,"language":466,"meta":349,"style":349},[325,2300,2301],{"__ignoreMap":349},[470,2302,2303,2305,2307,2309,2311,2313],{"class":472,"line":473},[470,2304,477],{"class":476},[470,2306,711],{"class":489},[470,2308,714],{"class":480},[470,2310,500],{"class":499},[470,2312,719],{"class":489},[470,2314,506],{"class":499},[723,2316,2317],{},[321,2318,2319],{},"If a secret ever gets committed, deleting the file is not enough: it stays in history. Treat the credential as compromised, rotate it first, then clean the history.",[321,2321,2322],{},[330,2323,2182],{},[343,2325,2327],{"className":2326,"code":735,"language":348,"meta":349},[346],[325,2328,735],{"__ignoreMap":349},[338,2330,2332,2333],{"id":2331},"step-3-create-the-github-repository-and-push-stage","Step 3: create the GitHub repository and push ",[325,2334,366],{},[321,2336,2337,2338,2341,2342,2344,2345,2347,2348,2350],{},"Create a ",[330,2339,2340],{},"private, empty"," repository: no README, no ",[325,2343,588],{},", no license. In the browser, use ",[330,2346,756],{},", choose ",[330,2349,760],{}," and leave the initialization options unchecked. Or with the CLI, from the project folder:",[343,2352,2353],{"className":464,"code":764,"language":466,"meta":349,"style":349},[325,2354,2355],{"__ignoreMap":349},[470,2356,2357,2359,2361,2363,2365,2367,2369],{"class":472,"line":473},[470,2358,445],{"class":476},[470,2360,773],{"class":489},[470,2362,776],{"class":489},[470,2364,779],{"class":489},[470,2366,782],{"class":480},[470,2368,785],{"class":480},[470,2370,788],{"class":480},[321,2372,2373,2374,2376],{},"The CLI adds the ",[325,2375,794],{}," remote for you. Without it, add the remote yourself, using the URL GitHub shows you:",[343,2378,2379],{"className":464,"code":798,"language":466,"meta":349,"style":349},[325,2380,2381],{"__ignoreMap":349},[470,2382,2383,2385,2387,2389,2391],{"class":472,"line":473},[470,2384,477],{"class":476},[470,2386,807],{"class":489},[470,2388,644],{"class":489},[470,2390,812],{"class":489},[470,2392,815],{"class":489},[321,2394,2395,2396,2398],{},"In both cases, publish ",[325,2397,366],{}," and set it to track the remote:",[343,2400,2401],{"className":464,"code":824,"language":466,"meta":349,"style":349},[325,2402,2403],{"__ignoreMap":349},[470,2404,2405,2407,2409,2411,2413],{"class":472,"line":473},[470,2406,477],{"class":476},[470,2408,833],{"class":489},[470,2410,836],{"class":480},[470,2412,812],{"class":489},[470,2414,540],{"class":489},[321,2416,2417],{},[330,2418,2182],{},[343,2420,2422],{"className":2421,"code":848,"language":348,"meta":349},[346],[325,2423,848],{"__ignoreMap":349},[338,2425,2427,2428],{"id":2426},"step-4-create-production","Step 4: create ",[325,2429,375],{},[321,2431,2432,2433,2435],{},"Branch it from ",[325,2434,366],{}," at the same commit, publish it, and go back:",[343,2437,2438],{"className":464,"code":865,"language":466,"meta":349,"style":349},[325,2439,2440,2450,2462],{"__ignoreMap":349},[470,2441,2442,2444,2446,2448],{"class":472,"line":473},[470,2443,477],{"class":476},[470,2445,874],{"class":489},[470,2447,877],{"class":480},[470,2449,880],{"class":489},[470,2451,2452,2454,2456,2458,2460],{"class":472,"line":484},[470,2453,477],{"class":476},[470,2455,833],{"class":489},[470,2457,836],{"class":480},[470,2459,812],{"class":489},[470,2461,880],{"class":489},[470,2463,2464,2466,2468],{"class":472,"line":509},[470,2465,477],{"class":476},[470,2467,874],{"class":489},[470,2469,540],{"class":489},[321,2471,2472,2473,2475],{},"Now add a local guard so that ",[325,2474,375],{}," can only ever fast-forward. Git will refuse any merge that is not a fast-forward:",[343,2477,2478],{"className":464,"code":909,"language":466,"meta":349,"style":349},[325,2479,2480],{"__ignoreMap":349},[470,2481,2482,2484,2486,2488],{"class":472,"line":473},[470,2483,477],{"class":476},[470,2485,490],{"class":489},[470,2487,920],{"class":489},[470,2489,923],{"class":480},[321,2491,2492],{},[330,2493,2182],{},[343,2495,2497],{"className":2496,"code":931,"language":348,"meta":349},[346],[325,2498,931],{"__ignoreMap":349},[338,2500,2502,2503,2505,2506],{"id":2501},"step-5-make-stage-the-default-branch-and-remove-main","Step 5: make ",[325,2504,366],{}," the default branch and remove ",[325,2507,411],{},[321,2509,2510,2511,2513,2514,2517,2518,2520],{},"GitHub decides the default branch, not your local Git. Even in an empty repository the default may stay ",[325,2512,411],{},", so check it explicitly. GitHub requires ",[330,2515,2516],{},"more than one branch"," to change the default, which is why ",[325,2519,375],{}," had to exist first.",[321,2522,2523,2524,2526,2527,965,2529,2531],{},"In the browser: ",[330,2525,961],{},", click the switch icon, pick ",[325,2528,366],{},[330,2530,968],{},", and confirm. With the CLI:",[343,2533,2534],{"className":464,"code":972,"language":466,"meta":349,"style":349},[325,2535,2536],{"__ignoreMap":349},[470,2537,2538,2540,2542,2544,2546],{"class":472,"line":473},[470,2539,445],{"class":476},[470,2541,773],{"class":489},[470,2543,983],{"class":489},[470,2545,986],{"class":480},[470,2547,540],{"class":489},[321,2549,2550,2551,2553,2554,2557],{},"Then look at what exists on the remote and, if ",[325,2552,411],{}," is there (for example because the repository was created with a README), delete it. Do it ",[330,2555,2556],{},"after"," switching the default, never before:",[343,2559,2560],{"className":464,"code":1001,"language":466,"meta":349,"style":349},[325,2561,2562],{"__ignoreMap":349},[470,2563,2564,2566,2568,2570],{"class":472,"line":473},[470,2565,477],{"class":476},[470,2567,1010],{"class":489},[470,2569,1013],{"class":480},[470,2571,1016],{"class":489},[343,2573,2574],{"className":464,"code":1019,"language":466,"meta":349,"style":349},[325,2575,2576],{"__ignoreMap":349},[470,2577,2578,2580,2582,2584,2586],{"class":472,"line":473},[470,2579,477],{"class":476},[470,2581,833],{"class":489},[470,2583,812],{"class":489},[470,2585,1032],{"class":480},[470,2587,1035],{"class":489},[321,2589,2590],{},"Finally refresh your local pointer to the remote default:",[343,2592,2593],{"className":464,"code":1041,"language":466,"meta":349,"style":349},[325,2594,2595],{"__ignoreMap":349},[470,2596,2597,2599,2601,2603,2605],{"class":472,"line":473},[470,2598,477],{"class":476},[470,2600,807],{"class":489},[470,2602,1052],{"class":489},[470,2604,812],{"class":489},[470,2606,1057],{"class":480},[321,2608,2609,2610,2612,2613,2615],{},"If ",[325,2611,411],{}," was created with a README it has an unrelated history. Do not merge it into ",[325,2614,366],{},"; just delete it.",[321,2617,2618],{},[330,2619,2182],{},[343,2621,2623],{"className":2622,"code":1074,"language":348,"meta":349},[346],[325,2624,1074],{"__ignoreMap":349},[338,2626,2628],{"id":2627},"step-6-guard-rails-on-github-if-your-plan-has-them","Step 6: guard rails on GitHub (if your plan has them)",[321,2630,2631,2632,2634,2635,2637],{},"The local ",[325,2633,1086],{}," guard costs nothing and needs no plan. On GitHub, branch protection and rulesets depend on your plan and on the repository visibility (rulesets are documented for GitHub Team and GitHub Enterprise). If yours offers them, protect ",[325,2636,375],{}," with: block force pushes and restrict deletions. If not, the process and the local guard are enough as long as you never force-push.",[338,2639,2641],{"id":2640},"step-7-the-day-to-day-loop","Step 7: the day-to-day loop",[321,2643,2644,2645,2647],{},"Working alone, commit to ",[325,2646,366],{}," and push:",[343,2649,2650],{"className":464,"code":1103,"language":466,"meta":349,"style":349},[325,2651,2652,2660,2668,2682],{"__ignoreMap":349},[470,2653,2654,2656,2658],{"class":472,"line":473},[470,2655,477],{"class":476},[470,2657,874],{"class":489},[470,2659,540],{"class":489},[470,2661,2662,2664,2666],{"class":472,"line":484},[470,2663,477],{"class":476},[470,2665,644],{"class":489},[470,2667,647],{"class":480},[470,2669,2670,2672,2674,2676,2678,2680],{"class":472,"line":509},[470,2671,477],{"class":476},[470,2673,711],{"class":489},[470,2675,714],{"class":480},[470,2677,500],{"class":499},[470,2679,1134],{"class":489},[470,2681,506],{"class":499},[470,2683,2684,2686],{"class":472,"line":528},[470,2685,477],{"class":476},[470,2687,1143],{"class":489},[321,2689,2690,2691,1149],{},"Working with others, use short-lived branches and pull requests into ",[325,2692,366],{},[343,2694,2695],{"className":464,"code":1152,"language":466,"meta":349,"style":349},[325,2696,2697,2707],{"__ignoreMap":349},[470,2698,2699,2701,2703,2705],{"class":472,"line":473},[470,2700,477],{"class":476},[470,2702,874],{"class":489},[470,2704,877],{"class":480},[470,2706,1165],{"class":489},[470,2708,2709,2711,2713,2715,2717],{"class":472,"line":484},[470,2710,477],{"class":476},[470,2712,833],{"class":489},[470,2714,836],{"class":480},[470,2716,812],{"class":489},[470,2718,1165],{"class":489},[321,2720,1180,2721,401,2724,965,2726,965,2728,965,2730,2732],{},[455,2722,1186],{"href":1183,"rel":2723},[1185],[325,2725,1189],{},[325,2727,1192],{},[325,2729,1195],{},[325,2731,1198],{},"). Nuxfire's own history follows them, which keeps release notes almost free.",[338,2734,2736],{"id":2735},"step-8-the-gate-before-your-first-release","Step 8: the gate before your first release",[321,2738,2739,2740,2742],{},"Nothing reaches ",[325,2741,375],{}," until it passes the same checks every time:",[343,2744,2745],{"className":464,"code":1212,"language":466,"meta":349,"style":349},[325,2746,2747,2753,2761],{"__ignoreMap":349},[470,2748,2749,2751],{"class":472,"line":473},[470,2750,1219],{"class":476},[470,2752,1222],{"class":489},[470,2754,2755,2757,2759],{"class":472,"line":484},[470,2756,1219],{"class":476},[470,2758,1229],{"class":489},[470,2760,1232],{"class":489},[470,2762,2763,2765,2767,2769,2771,2773],{"class":472,"line":509},[470,2764,1219],{"class":476},[470,2766,1229],{"class":489},[470,2768,1241],{"class":489},[470,2770,1244],{"class":480},[470,2772,1247],{"class":480},[470,2774,880],{"class":489},[321,2776,2777,2779,2780,2782],{},[325,2778,1254],{}," is the deploy preflight: it validates your Cloudflare token, your ",[325,2781,1258],{}," values, your database secrets and the domain zone, and tells you the exact fix for each problem before you spend minutes on a failing deploy.",[321,2784,2785,2786,2789,2790,1149],{},"For a first product, deploy a ",[330,2787,2788],{},"validation stage"," before production. A non-production stage gets its own Workers, database connection and secrets, and answers on ",[325,2791,2792],{},"\u003Cstage>.dev.\u003Cyour-domain>",[343,2794,2795],{"className":464,"code":1272,"language":466,"meta":349,"style":349},[325,2796,2797],{"__ignoreMap":349},[470,2798,2799,2801,2803,2805,2807,2809],{"class":472,"line":473},[470,2800,1219],{"class":476},[470,2802,1229],{"class":489},[470,2804,1283],{"class":489},[470,2806,1244],{"class":480},[470,2808,1247],{"class":480},[470,2810,540],{"class":489},[321,2812,2281,2813,2815,2816,965,2818,2074,2820,2822,2823,2825,2826,2829,2830,1314],{},[325,2814,1294],{}," is required for the flag to reach the script. Secrets and the Cloudflare token are covered in ",[455,2817,35],{"href":36},[455,2819,43],{"href":44},[455,2821,51],{"href":52},". One production-only prerequisite is worth knowing now: the apex domain and ",[325,2824,1306],{}," must have ",[330,2827,2828],{},"no"," A, AAAA or CNAME record in your DNS, otherwise Cloudflare rejects the custom domain (error ",[325,2831,1313],{},[321,2833,2834],{},[330,2835,2182],{},[343,2837,2839],{"className":2838,"code":1322,"language":348,"meta":349},[346],[325,2840,1322],{"__ignoreMap":349},[338,2842,2844],{"id":2843},"step-9-the-first-release","Step 9: the first release",[321,2846,2847,2848,2850],{},"Everything is green on ",[325,2849,366],{},". Promote it:",[343,2852,2853],{"className":464,"code":1337,"language":466,"meta":349,"style":349},[325,2854,2855,2861],{"__ignoreMap":349},[470,2856,2857,2859],{"class":472,"line":473},[470,2858,477],{"class":476},[470,2860,1346],{"class":489},[470,2862,2863,2865,2867,2869],{"class":472,"line":484},[470,2864,477],{"class":476},[470,2866,833],{"class":489},[470,2868,812],{"class":489},[470,2870,540],{"class":489},[321,2872,2873,2875,2876,2878,2879,1149],{},[325,2874,1361],{}," must say the working tree is clean. Now fast-forward ",[325,2877,375],{}," to the exact same commit, without leaving ",[325,2880,366],{},[343,2882,2883],{"className":464,"code":1370,"language":466,"meta":349,"style":349},[325,2884,2885],{"__ignoreMap":349},[470,2886,2887,2889,2891,2893],{"class":472,"line":473},[470,2888,477],{"class":476},[470,2890,833],{"class":489},[470,2892,812],{"class":489},[470,2894,1383],{"class":489},[321,2896,2897,2898,2900],{},"That push only succeeds if it is a pure fast-forward. Verify both branches are identical, then switch to ",[325,2899,375],{},", because that is the branch you deploy from:",[343,2902,2903],{"className":464,"code":1392,"language":466,"meta":349,"style":349},[325,2904,2905,2913,2925,2933],{"__ignoreMap":349},[470,2906,2907,2909,2911],{"class":472,"line":473},[470,2908,477],{"class":476},[470,2910,1401],{"class":489},[470,2912,1016],{"class":489},[470,2914,2915,2917,2919,2921,2923],{"class":472,"line":484},[470,2916,477],{"class":476},[470,2918,654],{"class":489},[470,2920,1412],{"class":480},[470,2922,1415],{"class":489},[470,2924,1418],{"class":489},[470,2926,2927,2929,2931],{"class":472,"line":509},[470,2928,477],{"class":476},[470,2930,874],{"class":489},[470,2932,880],{"class":489},[470,2934,2935,2937,2939],{"class":472,"line":528},[470,2936,477],{"class":476},[470,2938,1433],{"class":489},[470,2940,923],{"class":480},[321,2942,2281,2943,2945],{},[325,2944,1440],{}," must print nothing. Run the preflight once more and deploy:",[343,2947,2948],{"className":464,"code":1444,"language":466,"meta":349,"style":349},[325,2949,2950,2964],{"__ignoreMap":349},[470,2951,2952,2954,2956,2958,2960,2962],{"class":472,"line":473},[470,2953,1219],{"class":476},[470,2955,1229],{"class":489},[470,2957,1241],{"class":489},[470,2959,1244],{"class":480},[470,2961,1247],{"class":480},[470,2963,880],{"class":489},[470,2965,2966,2968,2970,2972,2974,2976],{"class":472,"line":484},[470,2967,1219],{"class":476},[470,2969,1229],{"class":489},[470,2971,1283],{"class":489},[470,2973,1244],{"class":480},[470,2975,1247],{"class":480},[470,2977,880],{"class":489},[321,2979,2980],{},"Only after the deploy succeeds, tag the release and go back to work:",[343,2982,2983],{"className":464,"code":1480,"language":466,"meta":349,"style":349},[325,2984,2985,3003,3013],{"__ignoreMap":349},[470,2986,2987,2989,2991,2993,2995,2997,2999,3001],{"class":472,"line":473},[470,2988,477],{"class":476},[470,2990,1489],{"class":489},[470,2992,1492],{"class":480},[470,2994,1495],{"class":489},[470,2996,714],{"class":480},[470,2998,500],{"class":499},[470,3000,1502],{"class":489},[470,3002,506],{"class":499},[470,3004,3005,3007,3009,3011],{"class":472,"line":484},[470,3006,477],{"class":476},[470,3008,833],{"class":489},[470,3010,812],{"class":489},[470,3012,1515],{"class":489},[470,3014,3015,3017,3019],{"class":472,"line":509},[470,3016,477],{"class":476},[470,3018,874],{"class":489},[470,3020,540],{"class":489},[321,3022,3023,3024,3026,3027,3029],{},"If the deploy fails, do not edit ",[325,3025,375],{},". Fix forward on ",[325,3028,366],{},", run the gate again and repeat the fast-forward. The branch can be briefly ahead of what is live, and the missing tag tells you exactly that.",[321,3031,3032],{},[330,3033,2182],{},[343,3035,3037],{"className":3036,"code":1540,"language":348,"meta":349},[346],[325,3038,1540],{"__ignoreMap":349},[338,3040,3042],{"id":3041},"step-10-keep-the-two-branches-from-drifting","Step 10: keep the two branches from drifting",[321,3044,3045,3046,3048,3049,3051],{},"The failure mode that bites everyone once: a fix lands on ",[325,3047,375],{}," (or gets deployed from ",[325,3050,366],{},") and the branches quietly stop describing the same code. The check is one line and it should always print nothing:",[343,3053,3054],{"className":464,"code":1558,"language":466,"meta":349,"style":349},[325,3055,3056,3064],{"__ignoreMap":349},[470,3057,3058,3060,3062],{"class":472,"line":473},[470,3059,477],{"class":476},[470,3061,1401],{"class":489},[470,3063,1016],{"class":489},[470,3065,3066,3068,3070,3072],{"class":472,"line":484},[470,3067,477],{"class":476},[470,3069,1575],{"class":489},[470,3071,1578],{"class":480},[470,3073,1581],{"class":489},[321,3075,3076,3077,3079,3080,3082],{},"If it prints commits, ",[325,3078,375],{}," has work ",[325,3081,366],{}," does not. A promotion will be rejected, and that is correct. Bring the work back, publish, and fast-forward again:",[343,3084,3085],{"className":464,"code":1593,"language":466,"meta":349,"style":349},[325,3086,3087,3095,3103,3113],{"__ignoreMap":349},[470,3088,3089,3091,3093],{"class":472,"line":473},[470,3090,477],{"class":476},[470,3092,874],{"class":489},[470,3094,540],{"class":489},[470,3096,3097,3099,3101],{"class":472,"line":484},[470,3098,477],{"class":476},[470,3100,1610],{"class":489},[470,3102,1418],{"class":489},[470,3104,3105,3107,3109,3111],{"class":472,"line":509},[470,3106,477],{"class":476},[470,3108,833],{"class":489},[470,3110,812],{"class":489},[470,3112,540],{"class":489},[470,3114,3115,3117,3119,3121],{"class":472,"line":528},[470,3116,477],{"class":476},[470,3118,833],{"class":489},[470,3120,812],{"class":489},[470,3122,1383],{"class":489},[321,3124,3125,3126,3128],{},"If the merge reports conflicts, resolve them, run the gate again and commit before pushing. Never resolve this with ",[325,3127,1638],{},": it would erase exactly the commits you are trying to keep.",[321,3130,3131],{},[330,3132,2182],{},[343,3134,3136],{"className":3135,"code":1647,"language":348,"meta":349},[346],[325,3137,1647],{"__ignoreMap":349},[338,3139,3141],{"id":3140},"rolling-back","Rolling back",[321,3143,3144,3145,3147],{},"Do not rewrite ",[325,3146,375],{},". Redeploy the code of an earlier tag instead:",[343,3149,3150],{"className":464,"code":1662,"language":466,"meta":349,"style":349},[325,3151,3152,3160,3170,3184],{"__ignoreMap":349},[470,3153,3154,3156,3158],{"class":472,"line":473},[470,3155,477],{"class":476},[470,3157,1489],{"class":489},[470,3159,1673],{"class":480},[470,3161,3162,3164,3166,3168],{"class":472,"line":484},[470,3163,477],{"class":476},[470,3165,874],{"class":489},[470,3167,1682],{"class":480},[470,3169,1515],{"class":489},[470,3171,3172,3174,3176,3178,3180,3182],{"class":472,"line":509},[470,3173,1219],{"class":476},[470,3175,1229],{"class":489},[470,3177,1283],{"class":489},[470,3179,1244],{"class":480},[470,3181,1247],{"class":480},[470,3183,880],{"class":489},[470,3185,3186,3188,3190],{"class":472,"line":528},[470,3187,477],{"class":476},[470,3189,874],{"class":489},[470,3191,540],{"class":489},[321,3193,3194,3195,3197,3198,3200],{},"This restores the ",[330,3196,325],{},". Database migrations are applied by every deploy and are not rolled back, so plan schema changes as additive first (add a column, ship code that uses it, remove the old one later). Then fix forward on ",[325,3199,366],{}," and promote a new release.",[321,3202,3203],{},[330,3204,2182],{},[343,3206,3208],{"className":3207,"code":1724,"language":348,"meta":349},[346],[325,3209,1724],{"__ignoreMap":349},[338,3211,3213],{"id":3212},"when-something-goes-wrong","When something goes wrong",[1732,3215,3216,3229],{},[1735,3217,3218],{},[1738,3219,3220,3223,3226],{},[1741,3221,3222],{},"What you see",[1741,3224,3225],{},"What it means",[1741,3227,3228],{},"What to do",[1751,3230,3231,3248,3262,3278,3295,3308],{},[1738,3232,3233,3237,3245],{},[1756,3234,3235],{},[325,3236,1760],{},[1756,3238,3239,3241,3242,3244],{},[325,3240,375],{}," has commits ",[325,3243,366],{}," lacks",[1756,3246,3247],{},"Step 10. Never force.",[1738,3249,3250,3254,3259],{},[1756,3251,3252],{},[325,3253,1778],{},[1756,3255,3256,3257],{},"The local guard stopped a merge on ",[325,3258,375],{},[1756,3260,3261],{},"Same cause and fix as above",[1738,3263,3264,3269,3272],{},[1756,3265,3266,3267],{},"GitHub will not let you delete ",[325,3268,411],{},[1756,3270,3271],{},"Usually because it is still the default branch",[1756,3273,3274,3275,3277],{},"Switch the default to ",[325,3276,366],{}," first (Step 5)",[1738,3279,3280,3287,3292],{},[1756,3281,372,3282,3284,3285],{},[325,3283,695],{}," file shows in ",[325,3286,1813],{},[1756,3288,3289,3291],{},[325,3290,588],{}," is missing a rule",[1756,3293,3294],{},"Fix it before the commit, never after",[1738,3296,3297,3300,3303],{},[1756,3298,3299],{},"You deployed from the wrong branch",[1756,3301,3302],{},"The deploy ships the checked-out tree",[1756,3304,3305,3307],{},[325,3306,1834],{}," before every deploy",[1738,3309,3310,3316,3319],{},[1756,3311,3312,3313,3315],{},"Domain error ",[325,3314,1313],{}," on the first production deploy",[1756,3317,3318],{},"The hostname already has a DNS record",[1756,3320,3321],{},"Delete the A, AAAA or CNAME record and deploy again",[338,3323,1853],{"id":1852},[424,3325,3327,3338,3349,3359,3371,3381,3389],{"className":3326},[1857],[359,3328,3330,3332,3333,2074,3335,3337],{"className":3329},[1861],[1863,3331],{"disabled":22,"type":1865}," Repository is private and only ",[325,3334,366],{},[325,3336,375],{}," exist on the remote",[359,3339,3341,688,3343,3345,3346,3348],{"className":3340},[1861],[1863,3342],{"disabled":22,"type":1865},[325,3344,366],{}," is the default branch and ",[325,3347,411],{}," is gone",[359,3350,3352,688,3354,3356,3357],{"className":3351},[1861],[1863,3353],{"disabled":22,"type":1865},[325,3355,1891],{}," prints ",[325,3358,1086],{},[359,3360,3362,3364,3365,3367,3368,3370],{"className":3361},[1861],[1863,3363],{"disabled":22,"type":1865}," No ",[325,3366,695],{}," file except ",[325,3369,691],{}," is tracked",[359,3372,3374,688,3376,2074,3378,3380],{"className":3373},[1861],[1863,3375],{"disabled":22,"type":1865},[325,3377,1914],{},[325,3379,1917],{}," are green",[359,3382,3384,688,3386,3388],{"className":3383},[1861],[1863,3385],{"disabled":22,"type":1865},[325,3387,1926],{}," prints nothing before you deploy",[359,3390,3392,3394],{"className":3391},[1861],[1863,3393],{"disabled":22,"type":1865}," The release is tagged only after the deploy succeeded",[321,3396,3397,3398,3400,3401,2074,3403,333],{},"Back to the setup: continue with ",[455,3399,35],{"href":36}," to create the API token, then ",[455,3402,43],{"href":44},[455,3404,51],{"href":52},[1945,3406,1947],{},{"title":349,"searchDepth":484,"depth":484,"links":3408},[3409,3410,3411,3412,3413,3415,3417,3419,3420,3421,3422,3423,3424,3425,3426],{"id":2011,"depth":484,"text":2012},{"id":2080,"depth":484,"text":2081},{"id":2111,"depth":484,"text":2112},{"id":2190,"depth":484,"text":2191},{"id":2331,"depth":484,"text":3414},"Step 3: create the GitHub repository and push stage",{"id":2426,"depth":484,"text":3416},"Step 4: create production",{"id":2501,"depth":484,"text":3418},"Step 5: make stage the default branch and remove main",{"id":2627,"depth":484,"text":2628},{"id":2640,"depth":484,"text":2641},{"id":2735,"depth":484,"text":2736},{"id":2843,"depth":484,"text":2844},{"id":3041,"depth":484,"text":3042},{"id":3140,"depth":484,"text":3141},{"id":3212,"depth":484,"text":3213},{"id":1852,"depth":484,"text":1853},"A senior-level manual for new Nuxfire owners: create the repository, make stage the default branch, remove main, promote to production by fast-forward and ship your first release safely.","en",{},"\u002Fblog\u002Fgit-branching-stage-production-first-release",{"title":1993,"description":3427},"blog\u002Fgit-branching-stage-production-first-release",[1981,1982,1983,1984,51,1985],{"en":1987,"pt-br":1988,"es":1989},"i0oJ4QAxOd1y67ziCUWahffPGYSpl8Opp2GSW_YSuD8",{"id":3437,"title":3438,"author":3439,"authorType":316,"body":3440,"category":1968,"date":1969,"description":4870,"extension":1971,"image":1972,"lang":4871,"meta":4872,"navigation":22,"ogImage":1975,"path":4873,"readTime":1977,"seo":4874,"stem":4875,"tags":4876,"translations":4877,"updated":316,"__hash__":4878},"blog\u002Fblog\u002Fgit-ramas-stage-production-primer-lanzamiento.md","Git para su Primer Lanzamiento: Ramas stage y production, del Repositorio Vacío a Producción","Equipo Nuxfire",{"type":318,"value":3441,"toc":4850},[3442,3451,3454,3458,3463,3466,3510,3522,3526,3554,3558,3612,3623,3627,3632,3636,3653,3658,3664,3722,3739,3757,3762,3766,3771,3777,3793,3813,3819,3835,3841,3857,3861,3866,3872,3878,3912,3918,3932,3936,3941,3950,3963,3974,3990,4000,4014,4030,4033,4049,4058,4062,4067,4071,4080,4084,4090,4130,4135,4161,4175,4179,4185,4217,4225,4235,4253,4275,4279,4284,4288,4294,4314,4324,4338,4344,4384,4389,4421,4424,4464,4473,4477,4482,4486,4495,4517,4526,4566,4572,4576,4581,4585,4591,4635,4644,4648,4653,4657,4766,4770,4838,4848],[321,3443,3444,3445,3447,3448,333],{},"Ha extraído el código fuente de Nuxfire y está a punto de desplegar su primer producto. Antes de ejecutar un solo comando ",[325,3446,327],{},", dedique veinte minutos a lo que decide si los próximos cien despliegues serán tranquilos o estresantes: ",[330,3449,3450],{},"cómo se relacionan sus ramas de Git con lo que está en producción",[321,3452,3453],{},"Este manual construye un modelo pequeño y estricto. Cada comando de Git que aparece aquí se ejecutó de principio a fin en un repositorio de prueba antes de publicarlo; las opciones de GitHub CLI provienen de su manual oficial. Cada paso incluye los comandos manuales y un prompt corto que puede pegar en un agente de IA de su IDE (Cursor, Claude Code, Copilot y similares).",[338,3455,3457],{"id":3456},"el-modelo-en-un-minuto","El modelo en un minuto",[343,3459,3461],{"className":3460,"code":347,"language":348,"meta":349},[346],[325,3462,347],{"__ignoreMap":349},[321,3464,3465],{},"Dos ramas de larga duración y cinco reglas:",[356,3467,3468,3476,3486,3494,3502],{},[359,3469,3470,3475],{},[330,3471,3472,3473,333],{},"Todo el trabajo ocurre en ",[325,3474,366],{}," Es la rama predeterminada, así que los pull requests y los clones llegan a ella.",[359,3477,3478,3485],{},[330,3479,3480,3482,3483,333],{},[325,3481,375],{}," solo avanza por fast-forward desde ",[325,3484,366],{}," Sin commits de merge y sin commits directos.",[359,3487,3488,3493],{},[330,3489,3490,3491,333],{},"Nunca haga force-push sobre ",[325,3492,375],{}," Si un push es rechazado, es la red de seguridad funcionando.",[359,3495,3496,3501],{},[330,3497,3498,3499,333],{},"Despliegue producción desde la rama ",[325,3500,375],{}," Un despliegue publica lo que tenga en checkout en su máquina, así que la rama en la que está es la verdad.",[359,3503,3504,401,3507,3509],{},[330,3505,3506],{},"Etiquete con un tag cada release que salió a producción",[325,3508,404],{},"). La rama dice \"aprobado\", el tag dice \"confirmado en producción\".",[321,3511,3512,3513,3515,3516,3518,3519,3521],{},"¿Por qué no conservar también ",[325,3514,411],{},"? Una tercera rama de larga duración es una tercera respuesta a \"¿cuál es el código más reciente?\". Nuxfire ya nombra sus etapas de despliegue ",[325,3517,366],{}," y ",[325,3520,375],{},", así que las ramas replican esos nombres y no hay nada que traducir mentalmente.",[338,3523,3525],{"id":3524},"qué-necesita","Qué necesita",[424,3527,3528,3539,3545],{},[359,3529,428,3530,3533,3534,3536,3537,333],{},[330,3531,3532],{},"2.28 o superior"," (para ",[325,3535,435],{},"). Compruébelo con ",[325,3538,439],{},[359,3540,3541,3542,3544],{},"Una cuenta de GitHub (personal u organización). GitHub CLI (",[325,3543,445],{},") es opcional; cada paso muestra también el camino por la interfaz web.",[359,3546,3547,3548,3550,3551,333],{},"La carpeta de Nuxfire extraída y ",[325,3549,452],{}," ejecutado, como en ",[455,3552,3553],{"href":32},"Instalación",[338,3555,3557],{"id":3556},"paso-1-dígale-a-git-quién-es-usted","Paso 1: dígale a Git quién es usted",[343,3559,3560],{"className":464,"code":465,"language":466,"meta":349,"style":349},[325,3561,3562,3568,3584,3600],{"__ignoreMap":349},[470,3563,3564,3566],{"class":472,"line":473},[470,3565,477],{"class":476},[470,3567,481],{"class":480},[470,3569,3570,3572,3574,3576,3578,3580,3582],{"class":472,"line":484},[470,3571,477],{"class":476},[470,3573,490],{"class":489},[470,3575,493],{"class":480},[470,3577,496],{"class":489},[470,3579,500],{"class":499},[470,3581,503],{"class":489},[470,3583,506],{"class":499},[470,3585,3586,3588,3590,3592,3594,3596,3598],{"class":472,"line":509},[470,3587,477],{"class":476},[470,3589,490],{"class":489},[470,3591,493],{"class":480},[470,3593,518],{"class":489},[470,3595,500],{"class":499},[470,3597,523],{"class":489},[470,3599,506],{"class":499},[470,3601,3602,3604,3606,3608,3610],{"class":472,"line":528},[470,3603,477],{"class":476},[470,3605,490],{"class":489},[470,3607,493],{"class":480},[470,3609,537],{"class":489},[470,3611,540],{"class":489},[321,3613,3614,3615,3617,3618,3620,3621,333],{},"La última línea hace que todo repositorio nuevo que cree empiece en ",[325,3616,366],{}," en lugar de ",[325,3619,549],{}," o ",[325,3622,411],{},[321,3624,3625],{},[330,3626,557],{},[343,3628,3630],{"className":3629,"code":561,"language":348,"meta":349},[346],[325,3631,561],{"__ignoreMap":349},[338,3633,3635],{"id":3634},"paso-2-proteja-sus-secretos-y-haga-el-primer-commit","Paso 2: proteja sus secretos y haga el primer commit",[321,3637,3638,3639,3641,3642,3518,3644,3646,3647,3649,3650,3652],{},"Su carpeta ya contiene archivos que ",[330,3640,573],{}," deben llegar a Git: ",[325,3643,577],{},[325,3645,580],{}," guardan credenciales reales (la comprobación previa del despliegue incluso verifica que los valores de la base de datos estén rellenos), y ",[325,3648,584],{}," es el estado local del despliegue. El ",[325,3651,588],{}," de Nuxfire los cubre:",[343,3654,3656],{"className":3655,"code":593,"language":348,"meta":349},[346],[325,3657,593],{"__ignoreMap":349},[321,3659,3660,3661,3663],{},"Cree el repositorio en ",[325,3662,366],{},", demuestre que las reglas funcionan y solo entonces añada todo:",[343,3665,3666],{"className":464,"code":604,"language":466,"meta":349,"style":349},[325,3667,3668,3678,3692,3700],{"__ignoreMap":349},[470,3669,3670,3672,3674,3676],{"class":472,"line":473},[470,3671,477],{"class":476},[470,3673,613],{"class":489},[470,3675,616],{"class":480},[470,3677,540],{"class":489},[470,3679,3680,3682,3684,3686,3688,3690],{"class":472,"line":484},[470,3681,477],{"class":476},[470,3683,625],{"class":489},[470,3685,628],{"class":480},[470,3687,631],{"class":489},[470,3689,634],{"class":489},[470,3691,637],{"class":489},[470,3693,3694,3696,3698],{"class":472,"line":509},[470,3695,477],{"class":476},[470,3697,644],{"class":489},[470,3699,647],{"class":480},[470,3701,3702,3704,3706,3708,3710,3712,3714,3716,3718,3720],{"class":472,"line":528},[470,3703,477],{"class":476},[470,3705,654],{"class":489},[470,3707,657],{"class":480},[470,3709,660],{"class":480},[470,3711,664],{"class":663},[470,3713,667],{"class":476},[470,3715,670],{"class":480},[470,3717,500],{"class":499},[470,3719,675],{"class":489},[470,3721,506],{"class":499},[321,3723,3724,3726,3727,688,3730,3732,3733,3735,3736,3738],{},[325,3725,683],{}," debe imprimir la regla que coincide con cada archivo. El último comando debe imprimir ",[330,3728,3729],{},"únicamente",[325,3731,691],{},". Si aparece cualquier otro archivo ",[325,3734,695],{},", deténgase y corrija ",[325,3737,588],{}," antes de hacer el commit.",[343,3740,3741],{"className":464,"code":702,"language":466,"meta":349,"style":349},[325,3742,3743],{"__ignoreMap":349},[470,3744,3745,3747,3749,3751,3753,3755],{"class":472,"line":473},[470,3746,477],{"class":476},[470,3748,711],{"class":489},[470,3750,714],{"class":480},[470,3752,500],{"class":499},[470,3754,719],{"class":489},[470,3756,506],{"class":499},[723,3758,3759],{},[321,3760,3761],{},"Si alguna vez se sube un secreto en un commit, borrar el archivo no basta: sigue en el historial. Trate la credencial como comprometida, rótela primero y solo después limpie el historial.",[321,3763,3764],{},[330,3765,557],{},[343,3767,3769],{"className":3768,"code":735,"language":348,"meta":349},[346],[325,3770,735],{"__ignoreMap":349},[338,3772,3774,3775],{"id":3773},"paso-3-cree-el-repositorio-en-github-y-suba-stage","Paso 3: cree el repositorio en GitHub y suba ",[325,3776,366],{},[321,3778,3779,3780,3783,3784,3786,3787,3789,3790,3792],{},"Cree un repositorio ",[330,3781,3782],{},"privado y vacío",": sin README, sin ",[325,3785,588],{},", sin licencia. En el navegador use ",[330,3788,756],{},", elija ",[330,3791,760],{}," y deje sin marcar las opciones de inicialización. O con la CLI, desde la carpeta del proyecto:",[343,3794,3795],{"className":464,"code":764,"language":466,"meta":349,"style":349},[325,3796,3797],{"__ignoreMap":349},[470,3798,3799,3801,3803,3805,3807,3809,3811],{"class":472,"line":473},[470,3800,445],{"class":476},[470,3802,773],{"class":489},[470,3804,776],{"class":489},[470,3806,779],{"class":489},[470,3808,782],{"class":480},[470,3810,785],{"class":480},[470,3812,788],{"class":480},[321,3814,3815,3816,3818],{},"La CLI añade el remoto ",[325,3817,794],{}," por usted. Sin ella, añádalo manualmente con la URL que le muestra GitHub:",[343,3820,3821],{"className":464,"code":798,"language":466,"meta":349,"style":349},[325,3822,3823],{"__ignoreMap":349},[470,3824,3825,3827,3829,3831,3833],{"class":472,"line":473},[470,3826,477],{"class":476},[470,3828,807],{"class":489},[470,3830,644],{"class":489},[470,3832,812],{"class":489},[470,3834,815],{"class":489},[321,3836,3837,3838,3840],{},"En ambos casos, publique ",[325,3839,366],{}," y configure el seguimiento del remoto:",[343,3842,3843],{"className":464,"code":824,"language":466,"meta":349,"style":349},[325,3844,3845],{"__ignoreMap":349},[470,3846,3847,3849,3851,3853,3855],{"class":472,"line":473},[470,3848,477],{"class":476},[470,3850,833],{"class":489},[470,3852,836],{"class":480},[470,3854,812],{"class":489},[470,3856,540],{"class":489},[321,3858,3859],{},[330,3860,557],{},[343,3862,3864],{"className":3863,"code":848,"language":348,"meta":349},[346],[325,3865,848],{"__ignoreMap":349},[338,3867,3869,3870],{"id":3868},"paso-4-cree-production","Paso 4: cree ",[325,3871,375],{},[321,3873,3874,3875,3877],{},"Cree la rama a partir de ",[325,3876,366],{},", en el mismo commit, publíquela y vuelva:",[343,3879,3880],{"className":464,"code":865,"language":466,"meta":349,"style":349},[325,3881,3882,3892,3904],{"__ignoreMap":349},[470,3883,3884,3886,3888,3890],{"class":472,"line":473},[470,3885,477],{"class":476},[470,3887,874],{"class":489},[470,3889,877],{"class":480},[470,3891,880],{"class":489},[470,3893,3894,3896,3898,3900,3902],{"class":472,"line":484},[470,3895,477],{"class":476},[470,3897,833],{"class":489},[470,3899,836],{"class":480},[470,3901,812],{"class":489},[470,3903,880],{"class":489},[470,3905,3906,3908,3910],{"class":472,"line":509},[470,3907,477],{"class":476},[470,3909,874],{"class":489},[470,3911,540],{"class":489},[321,3913,3914,3915,3917],{},"Ahora añada una protección local para que ",[325,3916,375],{}," solo pueda avanzar por fast-forward. Git rechazará cualquier merge que no sea un fast-forward:",[343,3919,3920],{"className":464,"code":909,"language":466,"meta":349,"style":349},[325,3921,3922],{"__ignoreMap":349},[470,3923,3924,3926,3928,3930],{"class":472,"line":473},[470,3925,477],{"class":476},[470,3927,490],{"class":489},[470,3929,920],{"class":489},[470,3931,923],{"class":480},[321,3933,3934],{},[330,3935,557],{},[343,3937,3939],{"className":3938,"code":931,"language":348,"meta":349},[346],[325,3940,931],{"__ignoreMap":349},[338,3942,3944,3945,3947,3948],{"id":3943},"paso-5-haga-de-stage-la-rama-predeterminada-y-elimine-main","Paso 5: haga de ",[325,3946,366],{}," la rama predeterminada y elimine ",[325,3949,411],{},[321,3951,3952,3953,3955,3956,3959,3960,3962],{},"Quien decide la rama predeterminada es GitHub, no su Git local. Incluso en un repositorio vacío puede seguir siendo ",[325,3954,411],{},", así que compruébelo de forma explícita. GitHub exige ",[330,3957,3958],{},"más de una rama"," para cambiar la predeterminada, y por eso ",[325,3961,375],{}," tenía que existir antes.",[321,3964,3965,3966,3968,3969,965,3971,3973],{},"En el navegador: ",[330,3967,961],{},", pulse el icono de cambio, elija ",[325,3970,366],{},[330,3972,968],{}," y confirme. Con la CLI:",[343,3975,3976],{"className":464,"code":972,"language":466,"meta":349,"style":349},[325,3977,3978],{"__ignoreMap":349},[470,3979,3980,3982,3984,3986,3988],{"class":472,"line":473},[470,3981,445],{"class":476},[470,3983,773],{"class":489},[470,3985,983],{"class":489},[470,3987,986],{"class":480},[470,3989,540],{"class":489},[321,3991,3992,3993,3995,3996,3999],{},"Después mire qué existe en el remoto y, si ",[325,3994,411],{}," está ahí (por ejemplo, porque el repositorio se creó con un README), elimínela. Hágalo ",[330,3997,3998],{},"después"," de cambiar la predeterminada, nunca antes:",[343,4001,4002],{"className":464,"code":1001,"language":466,"meta":349,"style":349},[325,4003,4004],{"__ignoreMap":349},[470,4005,4006,4008,4010,4012],{"class":472,"line":473},[470,4007,477],{"class":476},[470,4009,1010],{"class":489},[470,4011,1013],{"class":480},[470,4013,1016],{"class":489},[343,4015,4016],{"className":464,"code":1019,"language":466,"meta":349,"style":349},[325,4017,4018],{"__ignoreMap":349},[470,4019,4020,4022,4024,4026,4028],{"class":472,"line":473},[470,4021,477],{"class":476},[470,4023,833],{"class":489},[470,4025,812],{"class":489},[470,4027,1032],{"class":480},[470,4029,1035],{"class":489},[321,4031,4032],{},"Por último, actualice su puntero local a la rama predeterminada del remoto:",[343,4034,4035],{"className":464,"code":1041,"language":466,"meta":349,"style":349},[325,4036,4037],{"__ignoreMap":349},[470,4038,4039,4041,4043,4045,4047],{"class":472,"line":473},[470,4040,477],{"class":476},[470,4042,807],{"class":489},[470,4044,1052],{"class":489},[470,4046,812],{"class":489},[470,4048,1057],{"class":480},[321,4050,4051,4052,4054,4055,4057],{},"Si ",[325,4053,411],{}," se creó con un README, tiene un historial sin relación con el suyo. No la fusione en ",[325,4056,366],{},"; simplemente elimínela.",[321,4059,4060],{},[330,4061,557],{},[343,4063,4065],{"className":4064,"code":1074,"language":348,"meta":349},[346],[325,4066,1074],{"__ignoreMap":349},[338,4068,4070],{"id":4069},"paso-6-protecciones-en-github-si-su-plan-las-incluye","Paso 6: protecciones en GitHub (si su plan las incluye)",[321,4072,4073,4074,4076,4077,4079],{},"La protección local ",[325,4075,1086],{}," no cuesta nada y no depende de ningún plan. En GitHub, la protección de ramas y los rulesets dependen de su plan y de la visibilidad del repositorio (los rulesets están documentados para los planes GitHub Team y GitHub Enterprise). Si el suyo los ofrece, proteja ",[325,4078,375],{}," bloqueando los force pushes y restringiendo las eliminaciones. Si no, el proceso y la protección local bastan siempre que nunca haga force-push.",[338,4081,4083],{"id":4082},"paso-7-el-día-a-día","Paso 7: el día a día",[321,4085,4086,4087,4089],{},"Si trabaja solo, haga commit en ",[325,4088,366],{}," y súbalo:",[343,4091,4092],{"className":464,"code":1103,"language":466,"meta":349,"style":349},[325,4093,4094,4102,4110,4124],{"__ignoreMap":349},[470,4095,4096,4098,4100],{"class":472,"line":473},[470,4097,477],{"class":476},[470,4099,874],{"class":489},[470,4101,540],{"class":489},[470,4103,4104,4106,4108],{"class":472,"line":484},[470,4105,477],{"class":476},[470,4107,644],{"class":489},[470,4109,647],{"class":480},[470,4111,4112,4114,4116,4118,4120,4122],{"class":472,"line":509},[470,4113,477],{"class":476},[470,4115,711],{"class":489},[470,4117,714],{"class":480},[470,4119,500],{"class":499},[470,4121,1134],{"class":489},[470,4123,506],{"class":499},[470,4125,4126,4128],{"class":472,"line":528},[470,4127,477],{"class":476},[470,4129,1143],{"class":489},[321,4131,4132,4133,1149],{},"Si trabaja en equipo, use ramas cortas y pull requests hacia ",[325,4134,366],{},[343,4136,4137],{"className":464,"code":1152,"language":466,"meta":349,"style":349},[325,4138,4139,4149],{"__ignoreMap":349},[470,4140,4141,4143,4145,4147],{"class":472,"line":473},[470,4142,477],{"class":476},[470,4144,874],{"class":489},[470,4146,877],{"class":480},[470,4148,1165],{"class":489},[470,4150,4151,4153,4155,4157,4159],{"class":472,"line":484},[470,4152,477],{"class":476},[470,4154,833],{"class":489},[470,4156,836],{"class":480},[470,4158,812],{"class":489},[470,4160,1165],{"class":489},[321,4162,1180,4163,401,4166,965,4168,965,4170,965,4172,4174],{},[455,4164,1186],{"href":1183,"rel":4165},[1185],[325,4167,1189],{},[325,4169,1192],{},[325,4171,1195],{},[325,4173,1198],{},"). El historial del propio Nuxfire los sigue, lo que deja las notas de release casi gratis.",[338,4176,4178],{"id":4177},"paso-8-la-puerta-antes-de-su-primer-lanzamiento","Paso 8: la puerta antes de su primer lanzamiento",[321,4180,4181,4182,4184],{},"Nada llega a ",[325,4183,375],{}," sin pasar siempre por las mismas comprobaciones:",[343,4186,4187],{"className":464,"code":1212,"language":466,"meta":349,"style":349},[325,4188,4189,4195,4203],{"__ignoreMap":349},[470,4190,4191,4193],{"class":472,"line":473},[470,4192,1219],{"class":476},[470,4194,1222],{"class":489},[470,4196,4197,4199,4201],{"class":472,"line":484},[470,4198,1219],{"class":476},[470,4200,1229],{"class":489},[470,4202,1232],{"class":489},[470,4204,4205,4207,4209,4211,4213,4215],{"class":472,"line":509},[470,4206,1219],{"class":476},[470,4208,1229],{"class":489},[470,4210,1241],{"class":489},[470,4212,1244],{"class":480},[470,4214,1247],{"class":480},[470,4216,880],{"class":489},[321,4218,4219,4221,4222,4224],{},[325,4220,1254],{}," es la comprobación previa del despliegue: valida su token de Cloudflare, sus valores ",[325,4223,1258],{},", los secretos de la base de datos y la zona del dominio, e indica la corrección exacta de cada problema antes de que pierda minutos en un despliegue que va a fallar.",[321,4226,4227,4228,4231,4232,1149],{},"Para un primer producto, despliegue una ",[330,4229,4230],{},"etapa de validación"," antes de producción. Una etapa que no es de producción obtiene sus propios Workers, conexión de base de datos y secretos, y responde en ",[325,4233,4234],{},"\u003Cstage>.dev.\u003Csu-dominio>",[343,4236,4237],{"className":464,"code":1272,"language":466,"meta":349,"style":349},[325,4238,4239],{"__ignoreMap":349},[470,4240,4241,4243,4245,4247,4249,4251],{"class":472,"line":473},[470,4242,1219],{"class":476},[470,4244,1229],{"class":489},[470,4246,1283],{"class":489},[470,4248,1244],{"class":480},[470,4250,1247],{"class":480},[470,4252,540],{"class":489},[321,4254,4255,4256,4258,4259,965,4262,3518,4264,4267,4268,688,4270,4272,4273,1314],{},"El ",[325,4257,1294],{}," es necesario para que la opción llegue al script. El token y los secretos se explican en ",[455,4260,4261],{"href":36},"Acceso a Cloudflare",[455,4263,43],{"href":44},[455,4265,4266],{"href":52},"Despliegue",". Conviene conocer desde ya un requisito exclusivo de producción: el dominio raíz y ",[325,4269,1306],{},[330,4271,2828],{}," pueden tener ningún registro A, AAAA o CNAME en su DNS; de lo contrario, Cloudflare rechaza el dominio personalizado (error ",[325,4274,1313],{},[321,4276,4277],{},[330,4278,557],{},[343,4280,4282],{"className":4281,"code":1322,"language":348,"meta":349},[346],[325,4283,1322],{"__ignoreMap":349},[338,4285,4287],{"id":4286},"paso-9-el-primer-lanzamiento","Paso 9: el primer lanzamiento",[321,4289,4290,4291,4293],{},"Todo está en verde en ",[325,4292,366],{},". Promuévala:",[343,4295,4296],{"className":464,"code":1337,"language":466,"meta":349,"style":349},[325,4297,4298,4304],{"__ignoreMap":349},[470,4299,4300,4302],{"class":472,"line":473},[470,4301,477],{"class":476},[470,4303,1346],{"class":489},[470,4305,4306,4308,4310,4312],{"class":472,"line":484},[470,4307,477],{"class":476},[470,4309,833],{"class":489},[470,4311,812],{"class":489},[470,4313,540],{"class":489},[321,4315,4316,4318,4319,4321,4322,1149],{},[325,4317,1361],{}," debe indicar que el árbol de trabajo está limpio. Ahora avance ",[325,4320,375],{}," por fast-forward exactamente al mismo commit, sin salir de ",[325,4323,366],{},[343,4325,4326],{"className":464,"code":1370,"language":466,"meta":349,"style":349},[325,4327,4328],{"__ignoreMap":349},[470,4329,4330,4332,4334,4336],{"class":472,"line":473},[470,4331,477],{"class":476},[470,4333,833],{"class":489},[470,4335,812],{"class":489},[470,4337,1383],{"class":489},[321,4339,4340,4341,4343],{},"Ese push solo se acepta si es un fast-forward puro. Compruebe que las dos ramas son idénticas y cambie a ",[325,4342,375],{},", porque es la rama desde la que despliega:",[343,4345,4346],{"className":464,"code":1392,"language":466,"meta":349,"style":349},[325,4347,4348,4356,4368,4376],{"__ignoreMap":349},[470,4349,4350,4352,4354],{"class":472,"line":473},[470,4351,477],{"class":476},[470,4353,1401],{"class":489},[470,4355,1016],{"class":489},[470,4357,4358,4360,4362,4364,4366],{"class":472,"line":484},[470,4359,477],{"class":476},[470,4361,654],{"class":489},[470,4363,1412],{"class":480},[470,4365,1415],{"class":489},[470,4367,1418],{"class":489},[470,4369,4370,4372,4374],{"class":472,"line":509},[470,4371,477],{"class":476},[470,4373,874],{"class":489},[470,4375,880],{"class":489},[470,4377,4378,4380,4382],{"class":472,"line":528},[470,4379,477],{"class":476},[470,4381,1433],{"class":489},[470,4383,923],{"class":480},[321,4385,4255,4386,4388],{},[325,4387,1440],{}," no debe imprimir nada. Ejecute la comprobación previa una vez más y despliegue:",[343,4390,4391],{"className":464,"code":1444,"language":466,"meta":349,"style":349},[325,4392,4393,4407],{"__ignoreMap":349},[470,4394,4395,4397,4399,4401,4403,4405],{"class":472,"line":473},[470,4396,1219],{"class":476},[470,4398,1229],{"class":489},[470,4400,1241],{"class":489},[470,4402,1244],{"class":480},[470,4404,1247],{"class":480},[470,4406,880],{"class":489},[470,4408,4409,4411,4413,4415,4417,4419],{"class":472,"line":484},[470,4410,1219],{"class":476},[470,4412,1229],{"class":489},[470,4414,1283],{"class":489},[470,4416,1244],{"class":480},[470,4418,1247],{"class":480},[470,4420,880],{"class":489},[321,4422,4423],{},"Solo cuando el despliegue termine bien, etiquete el release y vuelva al trabajo:",[343,4425,4426],{"className":464,"code":1480,"language":466,"meta":349,"style":349},[325,4427,4428,4446,4456],{"__ignoreMap":349},[470,4429,4430,4432,4434,4436,4438,4440,4442,4444],{"class":472,"line":473},[470,4431,477],{"class":476},[470,4433,1489],{"class":489},[470,4435,1492],{"class":480},[470,4437,1495],{"class":489},[470,4439,714],{"class":480},[470,4441,500],{"class":499},[470,4443,1502],{"class":489},[470,4445,506],{"class":499},[470,4447,4448,4450,4452,4454],{"class":472,"line":484},[470,4449,477],{"class":476},[470,4451,833],{"class":489},[470,4453,812],{"class":489},[470,4455,1515],{"class":489},[470,4457,4458,4460,4462],{"class":472,"line":509},[470,4459,477],{"class":476},[470,4461,874],{"class":489},[470,4463,540],{"class":489},[321,4465,4466,4467,4469,4470,4472],{},"Si el despliegue falla, no edite ",[325,4468,375],{},". Corrija hacia adelante en ",[325,4471,366],{},", vuelva a pasar la puerta y repita el fast-forward. La rama puede quedar brevemente por delante de lo que está en producción, y la ausencia del tag se lo indica exactamente.",[321,4474,4475],{},[330,4476,557],{},[343,4478,4480],{"className":4479,"code":1540,"language":348,"meta":349},[346],[325,4481,1540],{"__ignoreMap":349},[338,4483,4485],{"id":4484},"paso-10-evite-que-las-dos-ramas-se-separen","Paso 10: evite que las dos ramas se separen",[321,4487,4488,4489,4491,4492,4494],{},"El modo de fallo que le ocurre a todo el mundo una vez: una corrección cae en ",[325,4490,375],{}," (o un despliegue sale de ",[325,4493,366],{},") y las ramas dejan de describir el mismo código sin que nadie lo note. La comprobación es una línea y siempre debe imprimir nada:",[343,4496,4497],{"className":464,"code":1558,"language":466,"meta":349,"style":349},[325,4498,4499,4507],{"__ignoreMap":349},[470,4500,4501,4503,4505],{"class":472,"line":473},[470,4502,477],{"class":476},[470,4504,1401],{"class":489},[470,4506,1016],{"class":489},[470,4508,4509,4511,4513,4515],{"class":472,"line":484},[470,4510,477],{"class":476},[470,4512,1575],{"class":489},[470,4514,1578],{"class":480},[470,4516,1581],{"class":489},[321,4518,4519,4520,4522,4523,4525],{},"Si imprime commits, ",[325,4521,375],{}," tiene trabajo que ",[325,4524,366],{}," no tiene. Una promoción será rechazada, y eso es correcto. Traiga el trabajo de vuelta, publíquelo y avance de nuevo:",[343,4527,4528],{"className":464,"code":1593,"language":466,"meta":349,"style":349},[325,4529,4530,4538,4546,4556],{"__ignoreMap":349},[470,4531,4532,4534,4536],{"class":472,"line":473},[470,4533,477],{"class":476},[470,4535,874],{"class":489},[470,4537,540],{"class":489},[470,4539,4540,4542,4544],{"class":472,"line":484},[470,4541,477],{"class":476},[470,4543,1610],{"class":489},[470,4545,1418],{"class":489},[470,4547,4548,4550,4552,4554],{"class":472,"line":509},[470,4549,477],{"class":476},[470,4551,833],{"class":489},[470,4553,812],{"class":489},[470,4555,540],{"class":489},[470,4557,4558,4560,4562,4564],{"class":472,"line":528},[470,4559,477],{"class":476},[470,4561,833],{"class":489},[470,4563,812],{"class":489},[470,4565,1383],{"class":489},[321,4567,4568,4569,4571],{},"Si el merge informa de conflictos, resuélvalos, vuelva a pasar la puerta y haga el commit antes de subir. Nunca lo resuelva con ",[325,4570,1638],{},": borraría justamente los commits que quiere conservar.",[321,4573,4574],{},[330,4575,557],{},[343,4577,4579],{"className":4578,"code":1647,"language":348,"meta":349},[346],[325,4580,1647],{"__ignoreMap":349},[338,4582,4584],{"id":4583},"volver-atrás","Volver atrás",[321,4586,4587,4588,4590],{},"No reescriba ",[325,4589,375],{},". Redespliegue el código de un tag anterior:",[343,4592,4593],{"className":464,"code":1662,"language":466,"meta":349,"style":349},[325,4594,4595,4603,4613,4627],{"__ignoreMap":349},[470,4596,4597,4599,4601],{"class":472,"line":473},[470,4598,477],{"class":476},[470,4600,1489],{"class":489},[470,4602,1673],{"class":480},[470,4604,4605,4607,4609,4611],{"class":472,"line":484},[470,4606,477],{"class":476},[470,4608,874],{"class":489},[470,4610,1682],{"class":480},[470,4612,1515],{"class":489},[470,4614,4615,4617,4619,4621,4623,4625],{"class":472,"line":509},[470,4616,1219],{"class":476},[470,4618,1229],{"class":489},[470,4620,1283],{"class":489},[470,4622,1244],{"class":480},[470,4624,1247],{"class":480},[470,4626,880],{"class":489},[470,4628,4629,4631,4633],{"class":472,"line":528},[470,4630,477],{"class":476},[470,4632,874],{"class":489},[470,4634,540],{"class":489},[321,4636,4637,4638,4640,4641,4643],{},"Esto restaura el ",[330,4639,1712],{},". Las migraciones de la base de datos se aplican en cada despliegue y no se revierten, así que planifique los cambios de esquema primero como aditivos (añada una columna, publique el código que la usa, elimine la antigua más tarde). Después corrija hacia adelante en ",[325,4642,366],{}," y promueva un nuevo release.",[321,4645,4646],{},[330,4647,557],{},[343,4649,4651],{"className":4650,"code":1724,"language":348,"meta":349},[346],[325,4652,1724],{"__ignoreMap":349},[338,4654,4656],{"id":4655},"cuando-algo-sale-mal","Cuando algo sale mal",[1732,4658,4659,4672],{},[1735,4660,4661],{},[1738,4662,4663,4666,4669],{},[1741,4664,4665],{},"Lo que ve",[1741,4667,4668],{},"Qué significa",[1741,4670,4671],{},"Qué hacer",[1751,4673,4674,4691,4705,4721,4739,4752],{},[1738,4675,4676,4680,4688],{},[1756,4677,4678],{},[325,4679,1760],{},[1756,4681,4682,4684,4685,4687],{},[325,4683,375],{}," tiene commits que ",[325,4686,366],{}," no tiene",[1756,4689,4690],{},"Paso 10. Nunca use force.",[1738,4692,4693,4697,4702],{},[1756,4694,4695],{},[325,4696,1778],{},[1756,4698,4699,4700],{},"La protección local detuvo un merge en ",[325,4701,375],{},[1756,4703,4704],{},"Misma causa y misma solución que arriba",[1738,4706,4707,4712,4715],{},[1756,4708,4709,4710],{},"GitHub no le deja eliminar ",[325,4711,411],{},[1756,4713,4714],{},"Normalmente sigue siendo la rama predeterminada",[1756,4716,4717,4718,4720],{},"Cambie primero la predeterminada a ",[325,4719,366],{}," (Paso 5)",[1738,4722,4723,4731,4736],{},[1756,4724,4725,4726,4728,4729],{},"Un archivo ",[325,4727,695],{}," aparece en ",[325,4730,1813],{},[1756,4732,4733,4734],{},"Falta una regla en ",[325,4735,588],{},[1756,4737,4738],{},"Corríjalo antes del commit, nunca después",[1738,4740,4741,4744,4747],{},[1756,4742,4743],{},"Desplegó desde la rama equivocada",[1756,4745,4746],{},"El despliegue publica el árbol en checkout",[1756,4748,4749,4751],{},[325,4750,1834],{}," antes de cada despliegue",[1738,4753,4754,4760,4763],{},[1756,4755,4756,4757,4759],{},"Error de dominio ",[325,4758,1313],{}," en el primer despliegue de producción",[1756,4761,4762],{},"El hostname ya tiene un registro DNS",[1756,4764,4765],{},"Elimine el registro A, AAAA o CNAME y despliegue de nuevo",[338,4767,4769],{"id":4768},"lista-de-verificación","Lista de verificación",[424,4771,4773,4783,4794,4803,4814,4824,4832],{"className":4772},[1857],[359,4774,4776,4778,4779,3518,4781],{"className":4775},[1861],[1863,4777],{"disabled":22,"type":1865}," El repositorio es privado y en el remoto solo existen ",[325,4780,366],{},[325,4782,375],{},[359,4784,4786,688,4788,4790,4791,4793],{"className":4785},[1861],[1863,4787],{"disabled":22,"type":1865},[325,4789,366],{}," es la rama predeterminada y ",[325,4792,411],{}," ya no existe",[359,4795,4797,688,4799,1892,4801],{"className":4796},[1861],[1863,4798],{"disabled":22,"type":1865},[325,4800,1891],{},[325,4802,1086],{},[359,4804,4806,4808,4809,4811,4812,1906],{"className":4805},[1861],[1863,4807],{"disabled":22,"type":1865}," Ningún archivo ",[325,4810,695],{},", salvo ",[325,4813,691],{},[359,4815,4817,688,4819,3518,4821,4823],{"className":4816},[1861],[1863,4818],{"disabled":22,"type":1865},[325,4820,1914],{},[325,4822,1917],{}," están en verde",[359,4825,4827,688,4829,4831],{"className":4826},[1861],[1863,4828],{"disabled":22,"type":1865},[325,4830,1926],{}," no imprime nada antes de desplegar",[359,4833,4835,4837],{"className":4834},[1861],[1863,4836],{"disabled":22,"type":1865}," El release solo se etiqueta después de que el despliegue termine bien",[321,4839,4840,4841,4843,4844,3518,4846,333],{},"De vuelta a la configuración: continúe con ",[455,4842,4261],{"href":36}," para crear el token de API, después ",[455,4845,43],{"href":44},[455,4847,4266],{"href":52},[1945,4849,1947],{},{"title":349,"searchDepth":484,"depth":484,"links":4851},[4852,4853,4854,4855,4856,4858,4860,4862,4863,4864,4865,4866,4867,4868,4869],{"id":3456,"depth":484,"text":3457},{"id":3524,"depth":484,"text":3525},{"id":3556,"depth":484,"text":3557},{"id":3634,"depth":484,"text":3635},{"id":3773,"depth":484,"text":4857},"Paso 3: cree el repositorio en GitHub y suba stage",{"id":3868,"depth":484,"text":4859},"Paso 4: cree production",{"id":3943,"depth":484,"text":4861},"Paso 5: haga de stage la rama predeterminada y elimine main",{"id":4069,"depth":484,"text":4070},{"id":4082,"depth":484,"text":4083},{"id":4177,"depth":484,"text":4178},{"id":4286,"depth":484,"text":4287},{"id":4484,"depth":484,"text":4485},{"id":4583,"depth":484,"text":4584},{"id":4655,"depth":484,"text":4656},{"id":4768,"depth":484,"text":4769},"Un manual de nivel sénior para quien acaba de adquirir Nuxfire: cree el repositorio, haga de stage la rama predeterminada, elimine main, promueva a production con fast-forward y publique su primera versión de forma segura.","es",{},"\u002Fblog\u002Fgit-ramas-stage-production-primer-lanzamiento",{"title":3438,"description":4870},"blog\u002Fgit-ramas-stage-production-primer-lanzamiento",[1981,1982,1983,1984,51,1985],{"en":1987,"pt-br":1988,"es":1989},"kFgSXbdgvKLfNJ0h-Dr_ifjZNpygdBR-M903HvaAoLk",{"id":4880,"title":4881,"author":3439,"authorType":316,"body":4882,"category":5400,"date":5401,"description":5402,"extension":1971,"image":5403,"lang":4871,"meta":5404,"navigation":22,"ogImage":5405,"path":5406,"readTime":5407,"seo":5408,"stem":5409,"tags":5410,"translations":5414,"updated":316,"__hash__":5418},"blog\u002Fblog\u002Farquitectura-modular-nuxt-layers-b2b.md","Arquitectura Modular con Nuxt Layers: Organizando Funciones B2B sin Monolitos Espagueti",{"type":318,"value":4883,"toc":5392},[4884,4887,4902,4905,4909,4912,4942,4952,4958,4960,4967,4973,5197,5199,5206,5211,5342,5349,5351,5355,5378,5381,5389],[321,4885,4886],{},"A medida que un producto SaaS crece, la complejidad del código tiende a explotar. Los componentes de checkout se mezclan con las reglas de autenticación, las pantallas de administración comparten directorios con las de los usuarios comunes y un cambio en el modelo de base de datos rompe tres pantallas distintas sin previo aviso.",[321,4888,4889,4890,4893,4894,4897,4898,4901],{},"La solución de ",[330,4891,4892],{},"Nuxt 4"," para este dilema se llama ",[330,4895,4896],{},"Nuxt Layers",". En ",[330,4899,4900],{},"Nuxfire",", usamos Layers para construir módulos aislados que se pueden activar o desactivar con una sola línea de configuración.",[4903,4904],"hr",{},[338,4906,4908],{"id":4907},"qué-es-una-nuxt-layer","¿Qué es una Nuxt Layer?",[321,4910,4911],{},"Una Layer en Nuxt es un paquete completo y autónomo que puede contener:",[424,4913,4914,4924,4930,4936],{},[359,4915,4916,4917,4920,4921,1314],{},"Páginas (",[325,4918,4919],{},"pages\u002F",") y layouts (",[325,4922,4923],{},"layouts\u002F",[359,4925,4926,4927,1314],{},"Componentes Vue accesibles (",[325,4928,4929],{},"components\u002F",[359,4931,4932,4933,1314],{},"Rutas de servidor y procedures tRPC (",[325,4934,4935],{},"server\u002F",[359,4937,4938,4939,1314],{},"Fragmentos del schema de Drizzle ORM (",[325,4940,4941],{},"schema.ts",[321,4943,4944,4945,4948,4949,333],{},"Esto significa que la funcionalidad de ",[330,4946,4947],{},"Facturación (Billing)",", por ejemplo, existe totalmente contenida dentro de ",[325,4950,4951],{},"layers\u002Fbilling\u002F",[343,4953,4956],{"className":4954,"code":4955,"language":348},[346],"layers\u002F\n├── admin\u002F          # Panel de administración de la plataforma, MFA y registros de auditoría\n├── billing\u002F        # Planes de Stripe, checkout, webhooks y portal del cliente\n├── teams\u002F          # Multi-tenancy B2B, invitaciones de miembros y RBAC granular\n├── notifications\u002F  # Hub de WebSockets con Durable Objects e historial en SQLite\n└── kanban\u002F         # Módulo opcional de tableros y tarjetas interactivas\n",[325,4957,4955],{"__ignoreMap":349},[4903,4959],{},[338,4961,4963,4964],{"id":4962},"el-contrato-de-capas-nuxfiremanifest","El Contrato de Capas: ",[325,4965,4966],{},"@nuxfire\u002Fmanifest",[321,4968,4969,4970,1149],{},"Para que cada capa sea verdaderamente modular sin un acoplamiento rígido, cada layer exporta un manifiesto mediante el helper compartido ",[325,4971,4972],{},"defineLayerManifest()",[343,4974,4978],{"className":4975,"code":4976,"language":4977,"meta":349,"style":349},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark monokai","import { defineLayerManifest } from \"@nuxfire\u002Fmanifest\";\n\nexport default defineLayerManifest({\n  name: \"billing\",\n  description: \"Gestión de suscripciones de Stripe, webhooks y portal de clientes\",\n  dependencies: [\"teams\"],\n  routes: [\"\u002Fbilling\", \"\u002Fbilling\u002Fplans\"],\n  navigation: {\n    title: \"Suscripción y Facturación\",\n    icon: \"i-lucide-credit-card\",\n    path: \"\u002Fbilling\"\n  }\n});\n","typescript",[325,4979,4980,5010,5015,5032,5050,5067,5090,5121,5132,5149,5166,5180,5186],{"__ignoreMap":349},[470,4981,4982,4986,4990,4994,4997,5000,5002,5004,5007],{"class":472,"line":473},[470,4983,4985],{"class":4984},"sRxSC","import",[470,4987,4989],{"class":4988},"swvn1"," {",[470,4991,4993],{"class":4992},"ss--_"," defineLayerManifest",[470,4995,4996],{"class":4988}," }",[470,4998,4999],{"class":4984}," from",[470,5001,500],{"class":499},[470,5003,4966],{"class":489},[470,5005,5006],{"class":499},"\"",[470,5008,5009],{"class":4988},";\n",[470,5011,5012],{"class":472,"line":484},[470,5013,5014],{"emptyLinePlaceholder":22},"\n",[470,5016,5017,5020,5023,5026,5029],{"class":472,"line":509},[470,5018,5019],{"class":4984},"export",[470,5021,5022],{"class":4984}," default",[470,5024,4993],{"class":5025},"sD0ED",[470,5027,5028],{"class":4992},"(",[470,5030,5031],{"class":4988},"{\n",[470,5033,5034,5038,5040,5042,5045,5047],{"class":472,"line":528},[470,5035,5037],{"class":5036},"squCx","  name",[470,5039,1149],{"class":4988},[470,5041,500],{"class":499},[470,5043,5044],{"class":489},"billing",[470,5046,5006],{"class":499},[470,5048,5049],{"class":4988},",\n",[470,5051,5053,5056,5058,5060,5063,5065],{"class":472,"line":5052},5,[470,5054,5055],{"class":5036},"  description",[470,5057,1149],{"class":4988},[470,5059,500],{"class":499},[470,5061,5062],{"class":489},"Gestión de suscripciones de Stripe, webhooks y portal de clientes",[470,5064,5006],{"class":499},[470,5066,5049],{"class":4988},[470,5068,5070,5073,5075,5078,5080,5083,5085,5088],{"class":472,"line":5069},6,[470,5071,5072],{"class":5036},"  dependencies",[470,5074,1149],{"class":4988},[470,5076,5077],{"class":4992}," [",[470,5079,5006],{"class":499},[470,5081,5082],{"class":489},"teams",[470,5084,5006],{"class":499},[470,5086,5087],{"class":4992},"]",[470,5089,5049],{"class":4988},[470,5091,5093,5096,5098,5100,5102,5105,5107,5110,5112,5115,5117,5119],{"class":472,"line":5092},7,[470,5094,5095],{"class":5036},"  routes",[470,5097,1149],{"class":4988},[470,5099,5077],{"class":4992},[470,5101,5006],{"class":499},[470,5103,5104],{"class":489},"\u002Fbilling",[470,5106,5006],{"class":499},[470,5108,5109],{"class":4988},",",[470,5111,500],{"class":499},[470,5113,5114],{"class":489},"\u002Fbilling\u002Fplans",[470,5116,5006],{"class":499},[470,5118,5087],{"class":4992},[470,5120,5049],{"class":4988},[470,5122,5124,5127,5129],{"class":472,"line":5123},8,[470,5125,5126],{"class":5036},"  navigation",[470,5128,1149],{"class":4988},[470,5130,5131],{"class":4988}," {\n",[470,5133,5135,5138,5140,5142,5145,5147],{"class":472,"line":5134},9,[470,5136,5137],{"class":5036},"    title",[470,5139,1149],{"class":4988},[470,5141,500],{"class":499},[470,5143,5144],{"class":489},"Suscripción y Facturación",[470,5146,5006],{"class":499},[470,5148,5049],{"class":4988},[470,5150,5152,5155,5157,5159,5162,5164],{"class":472,"line":5151},10,[470,5153,5154],{"class":5036},"    icon",[470,5156,1149],{"class":4988},[470,5158,500],{"class":499},[470,5160,5161],{"class":489},"i-lucide-credit-card",[470,5163,5006],{"class":499},[470,5165,5049],{"class":4988},[470,5167,5169,5172,5174,5176,5178],{"class":472,"line":5168},11,[470,5170,5171],{"class":5036},"    path",[470,5173,1149],{"class":4988},[470,5175,500],{"class":499},[470,5177,5104],{"class":489},[470,5179,506],{"class":499},[470,5181,5183],{"class":472,"line":5182},12,[470,5184,5185],{"class":4988},"  }\n",[470,5187,5189,5192,5195],{"class":472,"line":5188},13,[470,5190,5191],{"class":4988},"}",[470,5193,5194],{"class":4992},")",[470,5196,5009],{"class":4988},[4903,5198],{},[338,5200,5202,5203],{"id":5201},"activar-y-desactivar-módulos-en-configts","Activar y Desactivar Módulos en ",[325,5204,5205],{},"config.ts",[321,5207,5208,5209,1149],{},"Imagina que estás creando un producto ligero y no necesitas el módulo de Kanban ni el complejo sistema de notificaciones. En Nuxfire, controlas los módulos en el archivo raíz ",[325,5210,5205],{},[343,5212,5214],{"className":4975,"code":5213,"language":4977,"meta":349,"style":349},"export const config = {\n  domain: \"tusaas.com\",\n  layers: {\n    core: [\"teams\", \"billing\", \"notifications\", \"admin\"],\n    features: {\n      kanban: false, \u002F\u002F Desactívalo fácilmente sin romper el build\n    }\n  }\n};\n",[325,5215,5216,5232,5248,5257,5302,5311,5328,5333,5337],{"__ignoreMap":349},[470,5217,5218,5220,5224,5227,5230],{"class":472,"line":473},[470,5219,5019],{"class":4984},[470,5221,5223],{"class":5222},"srJo8"," const",[470,5225,490],{"class":5226},"s91G_",[470,5228,5229],{"class":663}," =",[470,5231,5131],{"class":4988},[470,5233,5234,5237,5239,5241,5244,5246],{"class":472,"line":484},[470,5235,5236],{"class":5036},"  domain",[470,5238,1149],{"class":4988},[470,5240,500],{"class":499},[470,5242,5243],{"class":489},"tusaas.com",[470,5245,5006],{"class":499},[470,5247,5049],{"class":4988},[470,5249,5250,5253,5255],{"class":472,"line":509},[470,5251,5252],{"class":5036},"  layers",[470,5254,1149],{"class":4988},[470,5256,5131],{"class":4988},[470,5258,5259,5262,5264,5266,5268,5270,5272,5274,5276,5278,5280,5282,5284,5287,5289,5291,5293,5296,5298,5300],{"class":472,"line":528},[470,5260,5261],{"class":5036},"    core",[470,5263,1149],{"class":4988},[470,5265,5077],{"class":4992},[470,5267,5006],{"class":499},[470,5269,5082],{"class":489},[470,5271,5006],{"class":499},[470,5273,5109],{"class":4988},[470,5275,500],{"class":499},[470,5277,5044],{"class":489},[470,5279,5006],{"class":499},[470,5281,5109],{"class":4988},[470,5283,500],{"class":499},[470,5285,5286],{"class":489},"notifications",[470,5288,5006],{"class":499},[470,5290,5109],{"class":4988},[470,5292,500],{"class":499},[470,5294,5295],{"class":489},"admin",[470,5297,5006],{"class":499},[470,5299,5087],{"class":4992},[470,5301,5049],{"class":4988},[470,5303,5304,5307,5309],{"class":472,"line":5052},[470,5305,5306],{"class":5036},"    features",[470,5308,1149],{"class":4988},[470,5310,5131],{"class":4988},[470,5312,5313,5316,5318,5322,5324],{"class":472,"line":5069},[470,5314,5315],{"class":5036},"      kanban",[470,5317,1149],{"class":4988},[470,5319,5321],{"class":5320},"s8HiA"," false",[470,5323,5109],{"class":4988},[470,5325,5327],{"class":5326},"ss7Ak"," \u002F\u002F Desactívalo fácilmente sin romper el build\n",[470,5329,5330],{"class":472,"line":5092},[470,5331,5332],{"class":4988},"    }\n",[470,5334,5335],{"class":472,"line":5123},[470,5336,5185],{"class":4988},[470,5338,5339],{"class":472,"line":5134},[470,5340,5341],{"class":4988},"};\n",[321,5343,5344,5345,5348],{},"El script de build inspecciona este archivo y arma la composición final en ",[325,5346,5347],{},"apps\u002Fapp\u002Fnuxt.config.ts"," de forma 100% automática.",[4903,5350],{},[338,5352,5354],{"id":5353},"beneficios-prácticos-para-equipos-y-desarrolladores","Beneficios Prácticos para Equipos y Desarrolladores",[356,5356,5357,5363,5369],{},[359,5358,5359,5362],{},[330,5360,5361],{},"Testabilidad Aislada",": Cada capa tiene sus propias pruebas unitarias y de integración.",[359,5364,5365,5368],{},[330,5366,5367],{},"Reutilización Inmediata",": Crea un nuevo producto copiando solo las capas que necesitas.",[359,5370,5371,5374,5375,5377],{},[330,5372,5373],{},"Contexto Ideal para las IA",": Cuando le pides a una IA (Cursor, Claude Code) que edite el checkout, solo necesita leer los archivos dentro de ",[325,5376,4951],{},", sin cargar en memoria miles de líneas irrelevantes.",[321,5379,5380],{},"¿Listo para transformar tu productividad y construir con la arquitectura correcta desde el principio?",[321,5382,5383],{},[455,5384,5386],{"href":5385},"\u002Fes#pricing",[330,5387,5388],{},"Obtén acceso inmediato al código fuente de Nuxfire y al Grupo VIP →",[1945,5390,5391],{},"html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s8HiA, html code.shiki .s8HiA{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}",{"title":349,"searchDepth":484,"depth":484,"links":5393},[5394,5395,5397,5399],{"id":4907,"depth":484,"text":4908},{"id":4962,"depth":484,"text":5396},"El Contrato de Capas: @nuxfire\u002Fmanifest",{"id":5201,"depth":484,"text":5398},"Activar y Desactivar Módulos en config.ts",{"id":5353,"depth":484,"text":5354},"Ingeniería de Software","2026-09-15","Cómo desacoplar la facturación con Stripe, los workspaces multi-tenant y el panel de administración con Nuxt Layers y monorepos Bun para crear SaaS limpios y sostenibles.","\u002Fimages\u002Fblog\u002Fnuxt-layers-modular-b2b-es.svg",{},"\u002Fimages\u002Fblog\u002Fnuxt-layers-modular-b2b-es.png","\u002Fblog\u002Farquitectura-modular-nuxt-layers-b2b","8 min",{"title":4881,"description":5402},"blog\u002Farquitectura-modular-nuxt-layers-b2b",[4896,5411,5412,5413],"Arquitectura Modular","B2B SaaS","Clean Code",{"en":5415,"pt-br":5416,"es":5417},"modular-architecture-nuxt-layers-b2b","arquitetura-modular-nuxt-layers-b2b","arquitectura-modular-nuxt-layers-b2b","LxIEll_aJYj2iZ_ENsnsY-_liUYDjuGF-q96AZLQFeQ",{"id":5420,"title":5421,"author":315,"authorType":316,"body":5422,"category":5858,"date":5401,"description":5859,"extension":1971,"image":5860,"lang":1973,"meta":5861,"navigation":22,"ogImage":5862,"path":5863,"readTime":5407,"seo":5864,"stem":5865,"tags":5866,"translations":5868,"updated":316,"__hash__":5869},"blog\u002Fblog\u002Farquitetura-modular-nuxt-layers-b2b.md","Arquitetura Modular com Nuxt Layers: Organizando Recursos B2B sem Monólitos Espaguete",{"type":318,"value":5423,"toc":5850},[5424,5427,5439,5441,5445,5448,5472,5481,5487,5489,5495,5500,5675,5677,5683,5688,5803,5808,5810,5814,5837,5840,5848],[321,5425,5426],{},"Conforme um produto SaaS cresce, a complexidade do código tende a explodir. Componentes de checkout misturam-se com regras de autenticação, telas de administração dividem diretórios com telas de usuários comuns e uma alteração no modelo de banco quebra três telas diferentes sem aviso prévio.",[321,5428,5429,5430,5432,5433,5435,5436,5438],{},"A solução do ",[330,5431,4892],{}," para esse dilema chama-se ",[330,5434,4896],{},". No ",[330,5437,4900],{},", usamos Layers para construir módulos isolados que podem ser ativados ou desativados com uma única linha de configuração.",[4903,5440],{},[338,5442,5444],{"id":5443},"o-que-é-uma-nuxt-layer","O que é uma Nuxt Layer?",[321,5446,5447],{},"Uma Layer no Nuxt é um pacote completo e autônomo que pode conter:",[424,5449,5450,5457,5462,5467],{},[359,5451,4916,5452,5454,5455,1314],{},[325,5453,4919],{},") e layouts (",[325,5456,4923],{},[359,5458,5459,5460,1314],{},"Componentes Vue acessíveis (",[325,5461,4929],{},[359,5463,5464,5465,1314],{},"Rotas de servidor e procedures tRPC (",[325,5466,4935],{},[359,5468,5469,5470,1314],{},"Fatias de schema do Drizzle ORM (",[325,5471,4941],{},[321,5473,5474,5475,5478,5479,333],{},"Isso significa que a funcionalidade de ",[330,5476,5477],{},"Faturamento (Billing)",", por exemplo, existe inteiramente contida dentro de ",[325,5480,4951],{},[343,5482,5485],{"className":5483,"code":5484,"language":348},[346],"layers\u002F\n├── admin\u002F          # Painel administrativo da plataforma, MFA e logs de auditoria\n├── billing\u002F        # Planos Stripe, checkout, webhooks e portal do cliente\n├── teams\u002F          # Multi-tenancy B2B, convites de membros e RBAC granular\n├── notifications\u002F  # Hub de WebSockets com Durable Objects e histórico SQLite\n└── kanban\u002F         # Módulo opcional de quadros e cards interativos\n",[325,5486,5484],{"__ignoreMap":349},[4903,5488],{},[338,5490,5492,5493],{"id":5491},"o-contrato-de-camadas-nuxfiremanifest","O Contrato de Camadas: ",[325,5494,4966],{},[321,5496,5497,5498,1149],{},"Para que cada camada seja verdadeiramente modular sem acoplamento rígido, cada layer exporta um manifesto usando o helper compartilhado ",[325,5499,4972],{},[343,5501,5503],{"className":4975,"code":5502,"language":4977,"meta":349,"style":349},"import { defineLayerManifest } from \"@nuxfire\u002Fmanifest\";\n\nexport default defineLayerManifest({\n  name: \"billing\",\n  description: \"Gerenciamento de assinaturas Stripe, webhooks e portal de clientes\",\n  dependencies: [\"teams\"],\n  routes: [\"\u002Fbilling\", \"\u002Fbilling\u002Fplans\"],\n  navigation: {\n    title: \"Assinatura & Faturamento\",\n    icon: \"i-lucide-credit-card\",\n    path: \"\u002Fbilling\"\n  }\n});\n",[325,5504,5505,5525,5529,5541,5555,5570,5588,5614,5622,5637,5651,5663,5667],{"__ignoreMap":349},[470,5506,5507,5509,5511,5513,5515,5517,5519,5521,5523],{"class":472,"line":473},[470,5508,4985],{"class":4984},[470,5510,4989],{"class":4988},[470,5512,4993],{"class":4992},[470,5514,4996],{"class":4988},[470,5516,4999],{"class":4984},[470,5518,500],{"class":499},[470,5520,4966],{"class":489},[470,5522,5006],{"class":499},[470,5524,5009],{"class":4988},[470,5526,5527],{"class":472,"line":484},[470,5528,5014],{"emptyLinePlaceholder":22},[470,5530,5531,5533,5535,5537,5539],{"class":472,"line":509},[470,5532,5019],{"class":4984},[470,5534,5022],{"class":4984},[470,5536,4993],{"class":5025},[470,5538,5028],{"class":4992},[470,5540,5031],{"class":4988},[470,5542,5543,5545,5547,5549,5551,5553],{"class":472,"line":528},[470,5544,5037],{"class":5036},[470,5546,1149],{"class":4988},[470,5548,500],{"class":499},[470,5550,5044],{"class":489},[470,5552,5006],{"class":499},[470,5554,5049],{"class":4988},[470,5556,5557,5559,5561,5563,5566,5568],{"class":472,"line":5052},[470,5558,5055],{"class":5036},[470,5560,1149],{"class":4988},[470,5562,500],{"class":499},[470,5564,5565],{"class":489},"Gerenciamento de assinaturas Stripe, webhooks e portal de clientes",[470,5567,5006],{"class":499},[470,5569,5049],{"class":4988},[470,5571,5572,5574,5576,5578,5580,5582,5584,5586],{"class":472,"line":5069},[470,5573,5072],{"class":5036},[470,5575,1149],{"class":4988},[470,5577,5077],{"class":4992},[470,5579,5006],{"class":499},[470,5581,5082],{"class":489},[470,5583,5006],{"class":499},[470,5585,5087],{"class":4992},[470,5587,5049],{"class":4988},[470,5589,5590,5592,5594,5596,5598,5600,5602,5604,5606,5608,5610,5612],{"class":472,"line":5092},[470,5591,5095],{"class":5036},[470,5593,1149],{"class":4988},[470,5595,5077],{"class":4992},[470,5597,5006],{"class":499},[470,5599,5104],{"class":489},[470,5601,5006],{"class":499},[470,5603,5109],{"class":4988},[470,5605,500],{"class":499},[470,5607,5114],{"class":489},[470,5609,5006],{"class":499},[470,5611,5087],{"class":4992},[470,5613,5049],{"class":4988},[470,5615,5616,5618,5620],{"class":472,"line":5123},[470,5617,5126],{"class":5036},[470,5619,1149],{"class":4988},[470,5621,5131],{"class":4988},[470,5623,5624,5626,5628,5630,5633,5635],{"class":472,"line":5134},[470,5625,5137],{"class":5036},[470,5627,1149],{"class":4988},[470,5629,500],{"class":499},[470,5631,5632],{"class":489},"Assinatura & Faturamento",[470,5634,5006],{"class":499},[470,5636,5049],{"class":4988},[470,5638,5639,5641,5643,5645,5647,5649],{"class":472,"line":5151},[470,5640,5154],{"class":5036},[470,5642,1149],{"class":4988},[470,5644,500],{"class":499},[470,5646,5161],{"class":489},[470,5648,5006],{"class":499},[470,5650,5049],{"class":4988},[470,5652,5653,5655,5657,5659,5661],{"class":472,"line":5168},[470,5654,5171],{"class":5036},[470,5656,1149],{"class":4988},[470,5658,500],{"class":499},[470,5660,5104],{"class":489},[470,5662,506],{"class":499},[470,5664,5665],{"class":472,"line":5182},[470,5666,5185],{"class":4988},[470,5668,5669,5671,5673],{"class":472,"line":5188},[470,5670,5191],{"class":4988},[470,5672,5194],{"class":4992},[470,5674,5009],{"class":4988},[4903,5676],{},[338,5678,5680,5681],{"id":5679},"ativando-e-desativando-módulos-em-configts","Ativando e Desativando Módulos em ",[325,5682,5205],{},[321,5684,5685,5686,1149],{},"Imagine que você está criando um produto enxuto e não precisa do módulo de Kanban ou do sistema de notificações complexo. No Nuxfire, você controla os módulos no arquivo raiz ",[325,5687,5205],{},[343,5689,5691],{"className":4975,"code":5690,"language":4977,"meta":349,"style":349},"export const config = {\n  domain: \"seusaas.com\",\n  layers: {\n    core: [\"teams\", \"billing\", \"notifications\", \"admin\"],\n    features: {\n      kanban: false, \u002F\u002F Desative com facilidade sem quebrar o build\n    }\n  }\n};\n",[325,5692,5693,5705,5720,5728,5770,5778,5791,5795,5799],{"__ignoreMap":349},[470,5694,5695,5697,5699,5701,5703],{"class":472,"line":473},[470,5696,5019],{"class":4984},[470,5698,5223],{"class":5222},[470,5700,490],{"class":5226},[470,5702,5229],{"class":663},[470,5704,5131],{"class":4988},[470,5706,5707,5709,5711,5713,5716,5718],{"class":472,"line":484},[470,5708,5236],{"class":5036},[470,5710,1149],{"class":4988},[470,5712,500],{"class":499},[470,5714,5715],{"class":489},"seusaas.com",[470,5717,5006],{"class":499},[470,5719,5049],{"class":4988},[470,5721,5722,5724,5726],{"class":472,"line":509},[470,5723,5252],{"class":5036},[470,5725,1149],{"class":4988},[470,5727,5131],{"class":4988},[470,5729,5730,5732,5734,5736,5738,5740,5742,5744,5746,5748,5750,5752,5754,5756,5758,5760,5762,5764,5766,5768],{"class":472,"line":528},[470,5731,5261],{"class":5036},[470,5733,1149],{"class":4988},[470,5735,5077],{"class":4992},[470,5737,5006],{"class":499},[470,5739,5082],{"class":489},[470,5741,5006],{"class":499},[470,5743,5109],{"class":4988},[470,5745,500],{"class":499},[470,5747,5044],{"class":489},[470,5749,5006],{"class":499},[470,5751,5109],{"class":4988},[470,5753,500],{"class":499},[470,5755,5286],{"class":489},[470,5757,5006],{"class":499},[470,5759,5109],{"class":4988},[470,5761,500],{"class":499},[470,5763,5295],{"class":489},[470,5765,5006],{"class":499},[470,5767,5087],{"class":4992},[470,5769,5049],{"class":4988},[470,5771,5772,5774,5776],{"class":472,"line":5052},[470,5773,5306],{"class":5036},[470,5775,1149],{"class":4988},[470,5777,5131],{"class":4988},[470,5779,5780,5782,5784,5786,5788],{"class":472,"line":5069},[470,5781,5315],{"class":5036},[470,5783,1149],{"class":4988},[470,5785,5321],{"class":5320},[470,5787,5109],{"class":4988},[470,5789,5790],{"class":5326}," \u002F\u002F Desative com facilidade sem quebrar o build\n",[470,5792,5793],{"class":472,"line":5092},[470,5794,5332],{"class":4988},[470,5796,5797],{"class":472,"line":5123},[470,5798,5185],{"class":4988},[470,5800,5801],{"class":472,"line":5134},[470,5802,5341],{"class":4988},[321,5804,5805,5806,5348],{},"O script de build inspeciona esse arquivo e monta a composição final em ",[325,5807,5347],{},[4903,5809],{},[338,5811,5813],{"id":5812},"benefícios-práticos-para-equipes-e-desenvolvedores","Benefícios Práticos para Equipes e Desenvolvedores",[356,5815,5816,5822,5828],{},[359,5817,5818,5821],{},[330,5819,5820],{},"Testabilidade Isolada",": Cada camada possui seus próprios testes unitários e de integração.",[359,5823,5824,5827],{},[330,5825,5826],{},"Reaproveitamento Imediato",": Crie um novo produto copiando apenas as camadas que você precisa.",[359,5829,5830,5833,5834,5836],{},[330,5831,5832],{},"Contexto Ideal para IAs",": Ao pedir para uma IA (Cursor, Claude Code) editar o checkout, ela só precisa ler os arquivos dentro de ",[325,5835,4951],{},", sem carregar milhares de linhas irrelevantes na memória.",[321,5838,5839],{},"Pronto para transformar sua produtividade e construir com a arquitetura correta desde o início?",[321,5841,5842],{},[455,5843,5845],{"href":5844},"\u002Fpt-br#pricing",[330,5846,5847],{},"Garanta acesso imediato ao código-fonte do Nuxfire e ao Grupo VIP →",[1945,5849,5391],{},{"title":349,"searchDepth":484,"depth":484,"links":5851},[5852,5853,5855,5857],{"id":5443,"depth":484,"text":5444},{"id":5491,"depth":484,"text":5854},"O Contrato de Camadas: @nuxfire\u002Fmanifest",{"id":5679,"depth":484,"text":5856},"Ativando e Desativando Módulos em config.ts",{"id":5812,"depth":484,"text":5813},"Engenharia de Software","Como desacoplar faturamento Stripe, workspaces multi-tenant e painel admin usando Nuxt Layers e monorepos Bun para criar SaaS limpos e sustentáveis.","\u002Fimages\u002Fblog\u002Fnuxt-layers-modular-b2b.svg",{},"\u002Fimages\u002Fblog\u002Fnuxt-layers-modular-b2b.png","\u002Fblog\u002Farquitetura-modular-nuxt-layers-b2b",{"title":5421,"description":5859},"blog\u002Farquitetura-modular-nuxt-layers-b2b",[4896,5867,5412,5413],"Arquitetura Modular",{"en":5415,"pt-br":5416,"es":5417},"MR1YvSIn0_FUQw4XWQ8s7MnrFlwkWASWIp5fxaYvx7o",{"id":5871,"title":5872,"author":1994,"authorType":316,"body":5873,"category":6330,"date":5401,"description":6331,"extension":1971,"image":6332,"lang":3428,"meta":6333,"navigation":22,"ogImage":6334,"path":6335,"readTime":6336,"seo":6337,"stem":6338,"tags":6339,"translations":6343,"updated":316,"__hash__":6347},"blog\u002Fblog\u002Fasync-queues-background-jobs-serverless.md","Asynchronous Processing on Serverless: Cloudflare Queues and Dead Letter Queues",{"type":318,"value":5874,"toc":6325},[5875,5878,5881,5893,5895,5899,5902,5908,5932,6160,6162,6166,6169,6172,6192,6280,6282,6286,6289,6300,6311,6314,6322],[321,5876,5877],{},"In modern web development, nothing degrades the user experience more than frozen screens waiting on slow operations.",[321,5879,5880],{},"Whether it is waiting 15 seconds for a language model response (an LLM such as Claude or GPT-4), generating a PDF report or sending hundreds of transactional emails, processing heavy tasks synchronously inside the HTTP request is a guaranteed recipe for timeouts and connection failures.",[321,5882,5883,5884,5886,5887,2074,5890,333],{},"In ",[330,5885,4900],{},", all background processing is handled resiliently by ",[330,5888,5889],{},"Cloudflare Queues",[330,5891,5892],{},"Dead Letter Queues (DLQ)",[4903,5894],{},[338,5896,5898],{"id":5897},"the-serverless-producer-consumer-architecture","The Serverless Producer-Consumer Architecture",[321,5900,5901],{},"In traditional architectures, setting up queues meant hosting RabbitMQ instances or managing Redis BullMQ queues with Node.js workers monitored by PM2.",[321,5903,5904,5905,5907],{},"With ",[330,5906,5889],{},", the infrastructure is 100% serverless:",[356,5909,5910,5920,5926],{},[359,5911,5912,5915,5916,5919],{},[330,5913,5914],{},"Producer",": The dashboard or API enqueues messages in milliseconds and returns an immediate response (",[325,5917,5918],{},"202 Accepted",") to the user.",[359,5921,5922,5925],{},[330,5923,5924],{},"Queue",": Cloudflare stores the messages with delivery guarantees and concurrency control.",[359,5927,5928,5931],{},[330,5929,5930],{},"Consumer (Worker)",": A dedicated worker processes batches of messages in the background with automatic retries.",[343,5933,5935],{"className":4975,"code":5934,"language":4977,"meta":349,"style":349},"\u002F\u002F Enqueuing an async job from a Nuxt 4 API\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  \n  \u002F\u002F Publishes to the Cloudflare queue in under 10ms\n  await env.APP_QUEUE.send({\n    type: \"PROCESS_AI_COMPLETION\",\n    payload: {\n      documentId: body.documentId,\n      prompt: body.prompt\n    },\n    enqueuedAt: Date.now()\n  });\n\n  return { success: true, message: \"Processing started in the background.\" };\n});\n",[325,5936,5937,5942,5970,5994,5999,6004,6026,6042,6051,6067,6081,6086,6104,6113,6118,6151],{"__ignoreMap":349},[470,5938,5939],{"class":472,"line":473},[470,5940,5941],{"class":5326},"\u002F\u002F Enqueuing an async job from a Nuxt 4 API\n",[470,5943,5944,5946,5948,5951,5953,5957,5959,5963,5965,5968],{"class":472,"line":484},[470,5945,5019],{"class":4984},[470,5947,5022],{"class":4984},[470,5949,5950],{"class":5025}," defineEventHandler",[470,5952,5028],{"class":4992},[470,5954,5956],{"class":5955},"sTNss","async",[470,5958,401],{"class":4988},[470,5960,5962],{"class":5961},"sQgqH","event",[470,5964,5194],{"class":4988},[470,5966,5967],{"class":5222}," =>",[470,5969,5131],{"class":4988},[470,5971,5972,5975,5978,5980,5983,5986,5988,5990,5992],{"class":472,"line":509},[470,5973,5974],{"class":5222},"  const",[470,5976,5977],{"class":5226}," body",[470,5979,5229],{"class":663},[470,5981,5982],{"class":4984}," await",[470,5984,5985],{"class":5025}," readBody",[470,5987,5028],{"class":5036},[470,5989,5962],{"class":4992},[470,5991,5194],{"class":5036},[470,5993,5009],{"class":4988},[470,5995,5996],{"class":472,"line":528},[470,5997,5998],{"class":5036},"  \n",[470,6000,6001],{"class":472,"line":5052},[470,6002,6003],{"class":5326},"  \u002F\u002F Publishes to the Cloudflare queue in under 10ms\n",[470,6005,6006,6009,6012,6014,6017,6019,6022,6024],{"class":472,"line":5069},[470,6007,6008],{"class":4984},"  await",[470,6010,6011],{"class":4992}," env",[470,6013,333],{"class":4988},[470,6015,6016],{"class":5226},"APP_QUEUE",[470,6018,333],{"class":4988},[470,6020,6021],{"class":5025},"send",[470,6023,5028],{"class":5036},[470,6025,5031],{"class":4988},[470,6027,6028,6031,6033,6035,6038,6040],{"class":472,"line":5092},[470,6029,6030],{"class":5036},"    type",[470,6032,1149],{"class":4988},[470,6034,500],{"class":499},[470,6036,6037],{"class":489},"PROCESS_AI_COMPLETION",[470,6039,5006],{"class":499},[470,6041,5049],{"class":4988},[470,6043,6044,6047,6049],{"class":472,"line":5123},[470,6045,6046],{"class":5036},"    payload",[470,6048,1149],{"class":4988},[470,6050,5131],{"class":4988},[470,6052,6053,6056,6058,6060,6062,6065],{"class":472,"line":5134},[470,6054,6055],{"class":5036},"      documentId",[470,6057,1149],{"class":4988},[470,6059,5977],{"class":4992},[470,6061,333],{"class":4988},[470,6063,6064],{"class":4992},"documentId",[470,6066,5049],{"class":4988},[470,6068,6069,6072,6074,6076,6078],{"class":472,"line":5151},[470,6070,6071],{"class":5036},"      prompt",[470,6073,1149],{"class":4988},[470,6075,5977],{"class":4992},[470,6077,333],{"class":4988},[470,6079,6080],{"class":4992},"prompt\n",[470,6082,6083],{"class":472,"line":5168},[470,6084,6085],{"class":4988},"    },\n",[470,6087,6088,6091,6093,6096,6098,6101],{"class":472,"line":5182},[470,6089,6090],{"class":5036},"    enqueuedAt",[470,6092,1149],{"class":4988},[470,6094,6095],{"class":4992}," Date",[470,6097,333],{"class":4988},[470,6099,6100],{"class":5025},"now",[470,6102,6103],{"class":5036},"()\n",[470,6105,6106,6109,6111],{"class":472,"line":5188},[470,6107,6108],{"class":4988},"  }",[470,6110,5194],{"class":5036},[470,6112,5009],{"class":4988},[470,6114,6116],{"class":472,"line":6115},14,[470,6117,5014],{"emptyLinePlaceholder":22},[470,6119,6121,6124,6126,6129,6131,6134,6136,6139,6141,6143,6146,6148],{"class":472,"line":6120},15,[470,6122,6123],{"class":4984},"  return",[470,6125,4989],{"class":4988},[470,6127,6128],{"class":5036}," success",[470,6130,1149],{"class":4988},[470,6132,6133],{"class":5320}," true",[470,6135,5109],{"class":4988},[470,6137,6138],{"class":5036}," message",[470,6140,1149],{"class":4988},[470,6142,500],{"class":499},[470,6144,6145],{"class":489},"Processing started in the background.",[470,6147,5006],{"class":499},[470,6149,6150],{"class":4988}," };\n",[470,6152,6154,6156,6158],{"class":472,"line":6153},16,[470,6155,5191],{"class":4988},[470,6157,5194],{"class":4992},[470,6159,5009],{"class":4988},[4903,6161],{},[338,6163,6165],{"id":6164},"resilience-with-exponential-backoff-and-dead-letter-queue-dlq","Resilience with Exponential Backoff and Dead Letter Queue (DLQ)",[321,6167,6168],{},"What if an external AI API or an email provider is temporarily down?",[321,6170,6171],{},"In Nuxfire, the queues are configured in SST with a strict fault-tolerance policy:",[424,6173,6174,6180,6186],{},[359,6175,6176,6179],{},[330,6177,6178],{},"Automatic retries",": The consumer retries the message up to 3 times at gradual intervals (Exponential Backoff).",[359,6181,6182,6185],{},[330,6183,6184],{},"Dead Letter Queue (DLQ)",": If the failure persists after the maximum attempts, the message is not lost! It is automatically sent to a quarantine queue (DLQ).",[359,6187,6188,6191],{},[330,6189,6190],{},"Inspection and Alerting",": The support team can inspect messages in the DLQ, fix the integration problem and safely re-enqueue the messages.",[343,6193,6195],{"className":4975,"code":6194,"language":4977,"meta":349,"style":349},"\u002F\u002F infra\u002Fqueue.ts using SST v4\nexport const appQueue = new sst.cloudflare.Queue(\"AppQueue\", {\n  dlq: new sst.cloudflare.Queue(\"AppQueueDLQ\")\n});\n",[325,6196,6197,6202,6241,6272],{"__ignoreMap":349},[470,6198,6199],{"class":472,"line":473},[470,6200,6201],{"class":5326},"\u002F\u002F infra\u002Fqueue.ts using SST v4\n",[470,6203,6204,6206,6208,6211,6213,6216,6219,6221,6224,6226,6228,6230,6232,6235,6237,6239],{"class":472,"line":484},[470,6205,5019],{"class":4984},[470,6207,5223],{"class":5222},[470,6209,6210],{"class":5226}," appQueue",[470,6212,5229],{"class":663},[470,6214,6215],{"class":663}," new",[470,6217,6218],{"class":4992}," sst",[470,6220,333],{"class":4988},[470,6222,6223],{"class":4992},"cloudflare",[470,6225,333],{"class":4988},[470,6227,5924],{"class":5025},[470,6229,5028],{"class":4992},[470,6231,5006],{"class":499},[470,6233,6234],{"class":489},"AppQueue",[470,6236,5006],{"class":499},[470,6238,5109],{"class":4988},[470,6240,5131],{"class":4988},[470,6242,6243,6246,6248,6250,6252,6254,6256,6258,6260,6262,6264,6267,6269],{"class":472,"line":509},[470,6244,6245],{"class":5036},"  dlq",[470,6247,1149],{"class":4988},[470,6249,6215],{"class":663},[470,6251,6218],{"class":4992},[470,6253,333],{"class":4988},[470,6255,6223],{"class":4992},[470,6257,333],{"class":4988},[470,6259,5924],{"class":5025},[470,6261,5028],{"class":4992},[470,6263,5006],{"class":499},[470,6265,6266],{"class":489},"AppQueueDLQ",[470,6268,5006],{"class":499},[470,6270,6271],{"class":4992},")\n",[470,6273,6274,6276,6278],{"class":472,"line":528},[470,6275,5191],{"class":4988},[470,6277,5194],{"class":4992},[470,6279,5009],{"class":4988},[4903,6281],{},[338,6283,6285],{"id":6284},"periodic-tasks-with-the-cron-worker","Periodic Tasks with the CRON Worker",[321,6287,6288],{},"Besides event-driven queues, many SaaS products need scheduled routines:",[424,6290,6291,6294,6297],{},[359,6292,6293],{},"Checking overdue invoices on Stripe.",[359,6295,6296],{},"Cleaning up expired invitation tokens.",[359,6298,6299],{},"Consolidating daily AI token usage metrics.",[321,6301,6302,6303,6306,6307,6310],{},"Nuxfire includes a native ",[330,6304,6305],{},"CRON Worker"," configured in ",[325,6308,6309],{},"apps\u002Ffunctions",", triggered every hour without depending on external services like Cronhook or cron instances on Linux.",[321,6312,6313],{},"Build professional distributed systems without operational surprises.",[321,6315,6316],{},[455,6317,6319],{"href":6318},"\u002F#pricing",[330,6320,6321],{},"Access the Nuxfire monorepo right now with immediate delivery and support in the VIP Group →",[1945,6323,6324],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .s8HiA, html code.shiki .s8HiA{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":349,"searchDepth":484,"depth":484,"links":6326},[6327,6328,6329],{"id":5897,"depth":484,"text":5898},{"id":6164,"depth":484,"text":6165},{"id":6284,"depth":484,"text":6285},"Distributed Systems","How to run long tasks, AI calls and bulk email sends without freezing the interface and without hitting HTTP timeouts on Cloudflare.","\u002Fimages\u002Fblog\u002Fcloudflare-queues-background-jobs-en.svg",{},"\u002Fimages\u002Fblog\u002Fcloudflare-queues-background-jobs-en.png","\u002Fblog\u002Fasync-queues-background-jobs-serverless","7 min",{"title":5872,"description":6331},"blog\u002Fasync-queues-background-jobs-serverless",[5889,6340,6341,6342],"Background Jobs","DLQ","Asynchronous",{"en":6344,"pt-br":6345,"es":6346},"async-queues-background-jobs-serverless","filas-assincronas-jobs-segundo-plano-serverless","colas-asincronas-jobs-segundo-plano-serverless","PXXe_OQYYKRORVlIQ7GIGGOnuCzC3e185sAoQ-OUAa4",{"id":6349,"title":6350,"author":1994,"authorType":316,"body":6351,"category":6938,"date":5401,"description":6939,"extension":1971,"image":6940,"lang":3428,"meta":6941,"navigation":22,"ogImage":6942,"path":6943,"readTime":6336,"seo":6944,"stem":6945,"tags":6946,"translations":6949,"updated":316,"__hash__":6953},"blog\u002Fblog\u002Fbuild-scalable-saas-nuxt-4-cloudflare.md","How to Build a Scalable SaaS with Nuxt 4 and Cloudflare Workers: The Definitive Guide",{"type":318,"value":6352,"toc":6930},[6353,6356,6370,6372,6376,6379,6382,6405,6410,6415,6434,6631,6633,6637,6646,6697,6722,6724,6728,6735,6749,6888,6890,6894,6900,6920,6927],[321,6354,6355],{},"Launching a SaaS product in 2026 takes much more than a good-looking interface. Users expect instant responses anywhere in the world, investors demand predictable operating costs, and developers do not want to lose weekends managing Kubernetes pods or paying astronomical bills for idle dedicated servers.",[321,6357,6358,6359,6361,6362,965,6364,2074,6367,333],{},"In this complete guide, we explore the architecture of ",[330,6360,4900],{},": a production-ready codebase built on ",[330,6363,4892],{},[330,6365,6366],{},"SST v4",[330,6368,6369],{},"Cloudflare Workers",[4903,6371],{},[338,6373,6375],{"id":6374},"why-has-traditional-saas-architecture-become-obsolete","Why Has Traditional SaaS Architecture Become Obsolete?",[321,6377,6378],{},"For years, the industry standard for new startups was to rent virtual instances (AWS EC2, DigitalOcean) or use PaaS providers with aggressive per-seat and usage-based pricing.",[321,6380,6381],{},"The problems of this traditional model show up quickly:",[356,6383,6384,6390,6399],{},[359,6385,6386,6389],{},[330,6387,6388],{},"Critical Cold Starts",": Traditional Lambdas take 800ms to 2s to warm up connections to databases and heavy modules.",[359,6391,6392,6395,6396,1314],{},[330,6393,6394],{},"Regional Latency",": Users in Latin America or Europe suffer from routes that have to hit servers in the United States (",[325,6397,6398],{},"us-east-1",[359,6400,6401,6404],{},[330,6402,6403],{},"Complex Scaling",": Configuring container autoscaling requires monitoring metrics, memory limits and costs that grow long before your revenue does.",[6406,6407,6409],"h3",{"id":6408},"the-alternative-global-edge-computing","The Alternative: Global Edge Computing",[321,6411,5904,6412,6414],{},[330,6413,6369],{},", your JavaScript\u002FTypeScript code runs directly on the V8 network distributed across more than 300 cities around the globe:",[424,6416,6417,6423,6428],{},[359,6418,6419,6422],{},[330,6420,6421],{},"0ms startup time"," (zero cold starts).",[359,6424,6425,333],{},[330,6426,6427],{},"Execution milliseconds away from the end user",[359,6429,6430,6433],{},[330,6431,6432],{},"Up to 80% lower operating cost"," than dedicated servers.",[343,6435,6437],{"className":4975,"code":6436,"language":4977,"meta":349,"style":349},"\u002F\u002F Example of a high-performance serverless endpoint in Nuxt 4\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event);\n  \n  \u002F\u002F Runs at the edge closest to the user, with a pooled connection through Hyperdrive\n  const team = await useDatabase().query.teams.findFirst({\n    where: eq(teams.ownerId, user.id)\n  });\n\n  return { team, edgeRegion: event.context.cloudflare?.region ?? \"edge\" };\n});\n",[325,6438,6439,6444,6466,6492,6496,6501,6536,6566,6574,6578,6623],{"__ignoreMap":349},[470,6440,6441],{"class":472,"line":473},[470,6442,6443],{"class":5326},"\u002F\u002F Example of a high-performance serverless endpoint in Nuxt 4\n",[470,6445,6446,6448,6450,6452,6454,6456,6458,6460,6462,6464],{"class":472,"line":484},[470,6447,5019],{"class":4984},[470,6449,5022],{"class":4984},[470,6451,5950],{"class":5025},[470,6453,5028],{"class":4992},[470,6455,5956],{"class":5955},[470,6457,401],{"class":4988},[470,6459,5962],{"class":5961},[470,6461,5194],{"class":4988},[470,6463,5967],{"class":5222},[470,6465,5131],{"class":4988},[470,6467,6468,6470,6472,6475,6477,6479,6481,6484,6486,6488,6490],{"class":472,"line":509},[470,6469,5974],{"class":5222},[470,6471,4989],{"class":4988},[470,6473,6474],{"class":5226}," user",[470,6476,4996],{"class":4988},[470,6478,5229],{"class":663},[470,6480,5982],{"class":4984},[470,6482,6483],{"class":5025}," requireUserSession",[470,6485,5028],{"class":5036},[470,6487,5962],{"class":4992},[470,6489,5194],{"class":5036},[470,6491,5009],{"class":4988},[470,6493,6494],{"class":472,"line":528},[470,6495,5998],{"class":5036},[470,6497,6498],{"class":472,"line":5052},[470,6499,6500],{"class":5326},"  \u002F\u002F Runs at the edge closest to the user, with a pooled connection through Hyperdrive\n",[470,6502,6503,6505,6508,6510,6512,6515,6518,6520,6523,6525,6527,6529,6532,6534],{"class":472,"line":5069},[470,6504,5974],{"class":5222},[470,6506,6507],{"class":5226}," team",[470,6509,5229],{"class":663},[470,6511,5982],{"class":4984},[470,6513,6514],{"class":5025}," useDatabase",[470,6516,6517],{"class":5036},"()",[470,6519,333],{"class":4988},[470,6521,6522],{"class":4992},"query",[470,6524,333],{"class":4988},[470,6526,5082],{"class":4992},[470,6528,333],{"class":4988},[470,6530,6531],{"class":5025},"findFirst",[470,6533,5028],{"class":5036},[470,6535,5031],{"class":4988},[470,6537,6538,6541,6543,6546,6548,6550,6552,6555,6557,6559,6561,6564],{"class":472,"line":5092},[470,6539,6540],{"class":5036},"    where",[470,6542,1149],{"class":4988},[470,6544,6545],{"class":5025}," eq",[470,6547,5028],{"class":5036},[470,6549,5082],{"class":4992},[470,6551,333],{"class":4988},[470,6553,6554],{"class":4992},"ownerId",[470,6556,5109],{"class":4988},[470,6558,6474],{"class":4992},[470,6560,333],{"class":4988},[470,6562,6563],{"class":4992},"id",[470,6565,6271],{"class":5036},[470,6567,6568,6570,6572],{"class":472,"line":5123},[470,6569,6108],{"class":4988},[470,6571,5194],{"class":5036},[470,6573,5009],{"class":4988},[470,6575,6576],{"class":472,"line":5134},[470,6577,5014],{"emptyLinePlaceholder":22},[470,6579,6580,6582,6584,6586,6588,6591,6593,6596,6598,6601,6603,6605,6608,6611,6614,6616,6619,6621],{"class":472,"line":5151},[470,6581,6123],{"class":4984},[470,6583,4989],{"class":4988},[470,6585,6507],{"class":4992},[470,6587,5109],{"class":4988},[470,6589,6590],{"class":5036}," edgeRegion",[470,6592,1149],{"class":4988},[470,6594,6595],{"class":4992}," event",[470,6597,333],{"class":4988},[470,6599,6600],{"class":4992},"context",[470,6602,333],{"class":4988},[470,6604,6223],{"class":4992},[470,6606,6607],{"class":4988},"?.",[470,6609,6610],{"class":4992},"region",[470,6612,6613],{"class":663}," ??",[470,6615,500],{"class":499},[470,6617,6618],{"class":489},"edge",[470,6620,5006],{"class":499},[470,6622,6150],{"class":4988},[470,6624,6625,6627,6629],{"class":472,"line":5168},[470,6626,5191],{"class":4988},[470,6628,5194],{"class":4992},[470,6630,5009],{"class":4988},[4903,6632],{},[338,6634,6636],{"id":6635},"the-bun-monorepo-the-professional-structure-of-an-enterprise-saas","The Bun Monorepo: The Professional Structure of an Enterprise SaaS",[321,6638,6639,6640,6642,6643,1149],{},"Many boilerplates package code as one messy monolithic block. In ",[330,6641,4900],{},", we adopted a monorepo orchestrated with ",[330,6644,6645],{},"Bun Workspaces",[424,6647,6648,6656,6664,6672,6679,6687],{},[359,6649,6650,6655],{},[330,6651,6652],{},[325,6653,6654],{},"apps\u002Fapp",": The main SaaS dashboard, with team layouts, end-to-end tRPC and account settings.",[359,6657,6658,6663],{},[330,6659,6660],{},[325,6661,6662],{},"apps\u002Fweb",": The public marketing portal with complete documentation, a high-conversion landing page and a blog.",[359,6665,6666,6671],{},[330,6667,6668],{},[325,6669,6670],{},"apps\u002Flogin",": A static, ultra-secure interface for OpenAuth.js, supporting magic links and social logins (Google, GitHub).",[359,6673,6674,6678],{},[330,6675,6676],{},[325,6677,6309],{},": Auxiliary Cloudflare Workers for async queues, periodic billing CRON and WebSockets.",[359,6680,6681,6686],{},[330,6682,6683],{},[325,6684,6685],{},"layers\u002F*",": Decoupled domain modules (Admin, Billing, Teams, Notifications).",[359,6688,6689,6694,6695,333],{},[330,6690,6691],{},[325,6692,6693],{},"infra\u002F*.ts",": All infrastructure declared as TypeScript code using ",[330,6696,6366],{},[343,6698,6700],{"className":464,"code":6699,"language":466,"meta":349,"style":349},"# Ultra-fast start in the Bun monorepo\nbun install\nbun run dev\n",[325,6701,6702,6707,6713],{"__ignoreMap":349},[470,6703,6704],{"class":472,"line":473},[470,6705,6706],{"class":5326},"# Ultra-fast start in the Bun monorepo\n",[470,6708,6709,6711],{"class":472,"line":484},[470,6710,1219],{"class":476},[470,6712,1222],{"class":489},[470,6714,6715,6717,6719],{"class":472,"line":509},[470,6716,1219],{"class":476},[470,6718,1229],{"class":489},[470,6720,6721],{"class":489}," dev\n",[4903,6723],{},[338,6725,6727],{"id":6726},"connecting-postgresql-to-the-edge-with-cloudflare-hyperdrive","Connecting PostgreSQL to the Edge with Cloudflare Hyperdrive",[321,6729,6730,6731,6734],{},"The biggest myth about relational databases and Workers was that direct connections would exhaust the PostgreSQL pool. With ",[330,6732,6733],{},"Cloudflare Hyperdrive",", your Postgres instances connect to the edge with:",[424,6736,6737,6740,6743],{},[359,6738,6739],{},"Smart pooling of reused TCP connections.",[359,6741,6742],{},"Read-query caching close to the user.",[359,6744,6745,6746,333],{},"Full compatibility with typed queries using ",[330,6747,6748],{},"Drizzle ORM",[343,6750,6752],{"className":4975,"code":6751,"language":4977,"meta":349,"style":349},"import { drizzle } from \"drizzle-orm\u002Fpostgres-js\";\nimport postgres from \"postgres\";\n\nexport function createDatabaseClient(connectionString: string) {\n  const sql = postgres(connectionString, { max: 1 });\n  return drizzle(sql, { schema });\n}\n",[325,6753,6754,6776,6795,6799,6824,6859,6883],{"__ignoreMap":349},[470,6755,6756,6758,6760,6763,6765,6767,6769,6772,6774],{"class":472,"line":473},[470,6757,4985],{"class":4984},[470,6759,4989],{"class":4988},[470,6761,6762],{"class":4992}," drizzle",[470,6764,4996],{"class":4988},[470,6766,4999],{"class":4984},[470,6768,500],{"class":499},[470,6770,6771],{"class":489},"drizzle-orm\u002Fpostgres-js",[470,6773,5006],{"class":499},[470,6775,5009],{"class":4988},[470,6777,6778,6780,6783,6786,6788,6791,6793],{"class":472,"line":484},[470,6779,4985],{"class":4984},[470,6781,6782],{"class":4992}," postgres ",[470,6784,6785],{"class":4984},"from",[470,6787,500],{"class":499},[470,6789,6790],{"class":489},"postgres",[470,6792,5006],{"class":499},[470,6794,5009],{"class":4988},[470,6796,6797],{"class":472,"line":509},[470,6798,5014],{"emptyLinePlaceholder":22},[470,6800,6801,6803,6806,6809,6811,6814,6816,6820,6822],{"class":472,"line":528},[470,6802,5019],{"class":4984},[470,6804,6805],{"class":5222}," function",[470,6807,6808],{"class":5025}," createDatabaseClient",[470,6810,5028],{"class":4988},[470,6812,6813],{"class":5961},"connectionString",[470,6815,1149],{"class":663},[470,6817,6819],{"class":6818},"s_MOj"," string",[470,6821,5194],{"class":4988},[470,6823,5131],{"class":4988},[470,6825,6826,6828,6831,6833,6836,6838,6840,6842,6844,6847,6849,6853,6855,6857],{"class":472,"line":5052},[470,6827,5974],{"class":5222},[470,6829,6830],{"class":5226}," sql",[470,6832,5229],{"class":663},[470,6834,6835],{"class":5025}," postgres",[470,6837,5028],{"class":5036},[470,6839,6813],{"class":4992},[470,6841,5109],{"class":4988},[470,6843,4989],{"class":4988},[470,6845,6846],{"class":5036}," max",[470,6848,1149],{"class":4988},[470,6850,6852],{"class":6851},"sYThS"," 1",[470,6854,4996],{"class":4988},[470,6856,5194],{"class":5036},[470,6858,5009],{"class":4988},[470,6860,6861,6863,6865,6867,6870,6872,6874,6877,6879,6881],{"class":472,"line":5069},[470,6862,6123],{"class":4984},[470,6864,6762],{"class":5025},[470,6866,5028],{"class":5036},[470,6868,6869],{"class":4992},"sql",[470,6871,5109],{"class":4988},[470,6873,4989],{"class":4988},[470,6875,6876],{"class":4992}," schema",[470,6878,4996],{"class":4988},[470,6880,5194],{"class":5036},[470,6882,5009],{"class":4988},[470,6884,6885],{"class":472,"line":5092},[470,6886,6887],{"class":4988},"}\n",[4903,6889],{},[338,6891,6893],{"id":6892},"start-with-nuxfire-save-months-of-development","Start with Nuxfire: Save Months of Development",[321,6895,6896,6897,6899],{},"Instead of spending 3 to 6 months setting up authentication, WebSockets, background queues, Stripe billing and multi-tenant permissions from scratch, ",[330,6898,4900],{}," delivers all of that code ready, tested and strictly typed end to end.",[424,6901,6902,6908,6914],{},[359,6903,6904,6907],{},[330,6905,6906],{},"Full Source Code Access",": No closed private packages or per-developer fees.",[359,6909,6910,6913],{},[330,6911,6912],{},"Immediate Delivery",": Get immediate access to the repository and the official VIP Group right after confirmation.",[359,6915,6916,6919],{},[330,6917,6918],{},"Perpetual License",": Create unlimited commercial projects.",[321,6921,6922],{},[455,6923,6924],{"href":6318},[330,6925,6926],{},"Get Immediate Access to the Source Code at the Promotional Price →",[1945,6928,6929],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}",{"title":349,"searchDepth":484,"depth":484,"links":6931},[6932,6935,6936,6937],{"id":6374,"depth":484,"text":6375,"children":6933},[6934],{"id":6408,"depth":509,"text":6409},{"id":6635,"depth":484,"text":6636},{"id":6726,"depth":484,"text":6727},{"id":6892,"depth":484,"text":6893},"Edge Architecture","Learn how to structure a modern SaaS in 2026 with Nuxt 4, SST v4 and Cloudflare Workers. Zero cold starts, sub-50ms global latency and massive infrastructure savings.","\u002Fimages\u002Fblog\u002Fnuxt4-cloudflare-saas-en.svg",{},"\u002Fimages\u002Fblog\u002Fnuxt4-cloudflare-saas-en.png","\u002Fblog\u002Fbuild-scalable-saas-nuxt-4-cloudflare",{"title":6350,"description":6939},"blog\u002Fbuild-scalable-saas-nuxt-4-cloudflare",[4892,6369,6947,6948,6366],"SaaS","Serverless",{"en":6950,"pt-br":6951,"es":6952},"build-scalable-saas-nuxt-4-cloudflare","como-construir-saas-escalavel-nuxt-4-cloudflare","como-construir-saas-escalable-nuxt-4-cloudflare","QOfsE8PUUi55i1V4KqElzkBhY0IbQiZFnKpRRpvT7Sc",{"id":6955,"title":6956,"author":3439,"authorType":316,"body":6957,"category":7363,"date":5401,"description":7364,"extension":1971,"image":7365,"lang":4871,"meta":7366,"navigation":22,"ogImage":7367,"path":7368,"readTime":6336,"seo":7369,"stem":7370,"tags":7371,"translations":7373,"updated":316,"__hash__":7374},"blog\u002Fblog\u002Fcolas-asincronas-jobs-segundo-plano-serverless.md","Procesamiento Asíncrono en Serverless: Cloudflare Queues y Dead Letter Queues",{"type":318,"value":6958,"toc":7358},[6959,6962,6965,6976,6978,6982,6985,6991,7014,7206,7208,7212,7215,7218,7237,7317,7319,7323,7326,7337,7346,7349,7356],[321,6960,6961],{},"En el desarrollo de aplicaciones web modernas, nada degrada más la experiencia del usuario que las pantallas bloqueadas esperando operaciones lentas.",[321,6963,6964],{},"Ya sea esperar 15 segundos la respuesta de un modelo de lenguaje (un LLM como Claude o GPT-4), generar un informe en PDF o enviar cientos de correos transaccionales, procesar tareas pesadas de forma síncrona dentro de la petición HTTP es una receta segura para los timeouts y los fallos de conexión.",[321,6966,6967,6968,6970,6971,6973,6974,333],{},"En ",[330,6969,4900],{},", todo el procesamiento en segundo plano lo gestionan de forma resiliente ",[330,6972,5889],{}," y las ",[330,6975,5892],{},[4903,6977],{},[338,6979,6981],{"id":6980},"la-arquitectura-productor-consumidor-serverless","La Arquitectura Productor-Consumidor Serverless",[321,6983,6984],{},"En las arquitecturas tradicionales, configurar colas exigía alojar instancias de RabbitMQ o administrar colas Redis BullMQ con workers de Node.js monitoreados por PM2.",[321,6986,6987,6988,6990],{},"Con ",[330,6989,5889],{},", la infraestructura es 100 % serverless:",[356,6992,6993,7002,7008],{},[359,6994,6995,6998,6999,7001],{},[330,6996,6997],{},"Productor",": El dashboard o la API encola mensajes en milisegundos y devuelve una respuesta inmediata (",[325,7000,5918],{},") al usuario.",[359,7003,7004,7007],{},[330,7005,7006],{},"Cola (Queue)",": Cloudflare almacena los mensajes con garantía de entrega y control de concurrencia.",[359,7009,7010,7013],{},[330,7011,7012],{},"Consumidor (Worker)",": Un worker dedicado procesa los lotes de mensajes en segundo plano con reintentos automáticos.",[343,7015,7017],{"className":4975,"code":7016,"language":4977,"meta":349,"style":349},"\u002F\u002F Encolando un job asíncrono desde una API de Nuxt 4\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  \n  \u002F\u002F Publica en la cola de Cloudflare en menos de 10 ms\n  await env.APP_QUEUE.send({\n    type: \"PROCESS_AI_COMPLETION\",\n    payload: {\n      documentId: body.documentId,\n      prompt: body.prompt\n    },\n    enqueuedAt: Date.now()\n  });\n\n  return { success: true, message: \"Procesamiento iniciado en segundo plano.\" };\n});\n",[325,7018,7019,7024,7046,7066,7070,7075,7093,7107,7115,7129,7141,7145,7159,7167,7171,7198],{"__ignoreMap":349},[470,7020,7021],{"class":472,"line":473},[470,7022,7023],{"class":5326},"\u002F\u002F Encolando un job asíncrono desde una API de Nuxt 4\n",[470,7025,7026,7028,7030,7032,7034,7036,7038,7040,7042,7044],{"class":472,"line":484},[470,7027,5019],{"class":4984},[470,7029,5022],{"class":4984},[470,7031,5950],{"class":5025},[470,7033,5028],{"class":4992},[470,7035,5956],{"class":5955},[470,7037,401],{"class":4988},[470,7039,5962],{"class":5961},[470,7041,5194],{"class":4988},[470,7043,5967],{"class":5222},[470,7045,5131],{"class":4988},[470,7047,7048,7050,7052,7054,7056,7058,7060,7062,7064],{"class":472,"line":509},[470,7049,5974],{"class":5222},[470,7051,5977],{"class":5226},[470,7053,5229],{"class":663},[470,7055,5982],{"class":4984},[470,7057,5985],{"class":5025},[470,7059,5028],{"class":5036},[470,7061,5962],{"class":4992},[470,7063,5194],{"class":5036},[470,7065,5009],{"class":4988},[470,7067,7068],{"class":472,"line":528},[470,7069,5998],{"class":5036},[470,7071,7072],{"class":472,"line":5052},[470,7073,7074],{"class":5326},"  \u002F\u002F Publica en la cola de Cloudflare en menos de 10 ms\n",[470,7076,7077,7079,7081,7083,7085,7087,7089,7091],{"class":472,"line":5069},[470,7078,6008],{"class":4984},[470,7080,6011],{"class":4992},[470,7082,333],{"class":4988},[470,7084,6016],{"class":5226},[470,7086,333],{"class":4988},[470,7088,6021],{"class":5025},[470,7090,5028],{"class":5036},[470,7092,5031],{"class":4988},[470,7094,7095,7097,7099,7101,7103,7105],{"class":472,"line":5092},[470,7096,6030],{"class":5036},[470,7098,1149],{"class":4988},[470,7100,500],{"class":499},[470,7102,6037],{"class":489},[470,7104,5006],{"class":499},[470,7106,5049],{"class":4988},[470,7108,7109,7111,7113],{"class":472,"line":5123},[470,7110,6046],{"class":5036},[470,7112,1149],{"class":4988},[470,7114,5131],{"class":4988},[470,7116,7117,7119,7121,7123,7125,7127],{"class":472,"line":5134},[470,7118,6055],{"class":5036},[470,7120,1149],{"class":4988},[470,7122,5977],{"class":4992},[470,7124,333],{"class":4988},[470,7126,6064],{"class":4992},[470,7128,5049],{"class":4988},[470,7130,7131,7133,7135,7137,7139],{"class":472,"line":5151},[470,7132,6071],{"class":5036},[470,7134,1149],{"class":4988},[470,7136,5977],{"class":4992},[470,7138,333],{"class":4988},[470,7140,6080],{"class":4992},[470,7142,7143],{"class":472,"line":5168},[470,7144,6085],{"class":4988},[470,7146,7147,7149,7151,7153,7155,7157],{"class":472,"line":5182},[470,7148,6090],{"class":5036},[470,7150,1149],{"class":4988},[470,7152,6095],{"class":4992},[470,7154,333],{"class":4988},[470,7156,6100],{"class":5025},[470,7158,6103],{"class":5036},[470,7160,7161,7163,7165],{"class":472,"line":5188},[470,7162,6108],{"class":4988},[470,7164,5194],{"class":5036},[470,7166,5009],{"class":4988},[470,7168,7169],{"class":472,"line":6115},[470,7170,5014],{"emptyLinePlaceholder":22},[470,7172,7173,7175,7177,7179,7181,7183,7185,7187,7189,7191,7194,7196],{"class":472,"line":6120},[470,7174,6123],{"class":4984},[470,7176,4989],{"class":4988},[470,7178,6128],{"class":5036},[470,7180,1149],{"class":4988},[470,7182,6133],{"class":5320},[470,7184,5109],{"class":4988},[470,7186,6138],{"class":5036},[470,7188,1149],{"class":4988},[470,7190,500],{"class":499},[470,7192,7193],{"class":489},"Procesamiento iniciado en segundo plano.",[470,7195,5006],{"class":499},[470,7197,6150],{"class":4988},[470,7199,7200,7202,7204],{"class":472,"line":6153},[470,7201,5191],{"class":4988},[470,7203,5194],{"class":4992},[470,7205,5009],{"class":4988},[4903,7207],{},[338,7209,7211],{"id":7210},"resiliencia-con-backoff-exponencial-y-dead-letter-queue-dlq","Resiliencia con Backoff Exponencial y Dead Letter Queue (DLQ)",[321,7213,7214],{},"¿Y si una API externa de IA o un proveedor de correo está temporalmente fuera de servicio?",[321,7216,7217],{},"En Nuxfire, las colas se configuran en SST con una política estricta de tolerancia a fallos:",[424,7219,7220,7226,7231],{},[359,7221,7222,7225],{},[330,7223,7224],{},"Reintentos automáticos",": El consumidor intenta reprocesar el mensaje hasta 3 veces con intervalos graduales (Backoff Exponencial).",[359,7227,7228,7230],{},[330,7229,6184],{},": Si el fallo persiste tras los intentos máximos, ¡el mensaje no se pierde! Se envía automáticamente a una cola de cuarentena (DLQ).",[359,7232,7233,7236],{},[330,7234,7235],{},"Inspección y Alerta",": El equipo de soporte puede inspeccionar los mensajes en la DLQ, corregir el problema de integración y volver a encolar los mensajes con seguridad.",[343,7238,7240],{"className":4975,"code":7239,"language":4977,"meta":349,"style":349},"\u002F\u002F infra\u002Fqueue.ts usando SST v4\nexport const appQueue = new sst.cloudflare.Queue(\"AppQueue\", {\n  dlq: new sst.cloudflare.Queue(\"AppQueueDLQ\")\n});\n",[325,7241,7242,7247,7281,7309],{"__ignoreMap":349},[470,7243,7244],{"class":472,"line":473},[470,7245,7246],{"class":5326},"\u002F\u002F infra\u002Fqueue.ts usando SST v4\n",[470,7248,7249,7251,7253,7255,7257,7259,7261,7263,7265,7267,7269,7271,7273,7275,7277,7279],{"class":472,"line":484},[470,7250,5019],{"class":4984},[470,7252,5223],{"class":5222},[470,7254,6210],{"class":5226},[470,7256,5229],{"class":663},[470,7258,6215],{"class":663},[470,7260,6218],{"class":4992},[470,7262,333],{"class":4988},[470,7264,6223],{"class":4992},[470,7266,333],{"class":4988},[470,7268,5924],{"class":5025},[470,7270,5028],{"class":4992},[470,7272,5006],{"class":499},[470,7274,6234],{"class":489},[470,7276,5006],{"class":499},[470,7278,5109],{"class":4988},[470,7280,5131],{"class":4988},[470,7282,7283,7285,7287,7289,7291,7293,7295,7297,7299,7301,7303,7305,7307],{"class":472,"line":509},[470,7284,6245],{"class":5036},[470,7286,1149],{"class":4988},[470,7288,6215],{"class":663},[470,7290,6218],{"class":4992},[470,7292,333],{"class":4988},[470,7294,6223],{"class":4992},[470,7296,333],{"class":4988},[470,7298,5924],{"class":5025},[470,7300,5028],{"class":4992},[470,7302,5006],{"class":499},[470,7304,6266],{"class":489},[470,7306,5006],{"class":499},[470,7308,6271],{"class":4992},[470,7310,7311,7313,7315],{"class":472,"line":528},[470,7312,5191],{"class":4988},[470,7314,5194],{"class":4992},[470,7316,5009],{"class":4988},[4903,7318],{},[338,7320,7322],{"id":7321},"tareas-periódicas-con-el-cron-worker","Tareas Periódicas con el CRON Worker",[321,7324,7325],{},"Además de las colas orientadas a eventos, muchos SaaS necesitan rutinas programadas:",[424,7327,7328,7331,7334],{},[359,7329,7330],{},"Verificar las facturas vencidas en Stripe.",[359,7332,7333],{},"Limpiar los tokens de invitación expirados.",[359,7335,7336],{},"Consolidar las métricas de uso diario de tokens de IA.",[321,7338,7339,7340,7342,7343,7345],{},"Nuxfire incluye un ",[330,7341,6305],{}," nativo configurado en ",[325,7344,6309],{},", que se dispara cada hora sin depender de servicios externos como Cronhook ni de instancias de cron en Linux.",[321,7347,7348],{},"Construye sistemas distribuidos profesionales sin sorpresas operativas.",[321,7350,7351],{},[455,7352,7353],{"href":5385},[330,7354,7355],{},"Accede ahora mismo al monorepo de Nuxfire con entrega inmediata y soporte en el Grupo VIP →",[1945,7357,6324],{},{"title":349,"searchDepth":484,"depth":484,"links":7359},[7360,7361,7362],{"id":6980,"depth":484,"text":6981},{"id":7210,"depth":484,"text":7211},{"id":7321,"depth":484,"text":7322},"Sistemas Distribuidos","Cómo ejecutar tareas largas, llamadas a IA y envíos masivos de correos sin bloquear la interfaz y sin superar los timeouts HTTP en Cloudflare.","\u002Fimages\u002Fblog\u002Fcloudflare-queues-background-jobs-es.svg",{},"\u002Fimages\u002Fblog\u002Fcloudflare-queues-background-jobs-es.png","\u002Fblog\u002Fcolas-asincronas-jobs-segundo-plano-serverless",{"title":6956,"description":7364},"blog\u002Fcolas-asincronas-jobs-segundo-plano-serverless",[5889,6340,6341,7372],"Asíncrono",{"en":6344,"pt-br":6345,"es":6346},"WFcFYeBCagx72FaFBmbti_OkqJfbu1ZnqttWOKLRgYM",{"id":7376,"title":7377,"author":3439,"authorType":316,"body":7378,"category":7914,"date":5401,"description":7915,"extension":1971,"image":7916,"lang":4871,"meta":7917,"navigation":22,"ogImage":7918,"path":7919,"readTime":6336,"seo":7920,"stem":7921,"tags":7922,"translations":7923,"updated":316,"__hash__":7924},"blog\u002Fblog\u002Fcomo-construir-saas-escalable-nuxt-4-cloudflare.md","Cómo Construir un SaaS Escalable con Nuxt 4 y Cloudflare Workers: La Guía Definitiva",{"type":318,"value":7379,"toc":7906},[7380,7383,7395,7397,7401,7404,7407,7429,7433,7438,7457,7636,7638,7642,7650,7696,7720,7722,7726,7732,7745,7865,7867,7871,7877,7897,7904],[321,7381,7382],{},"Lanzar un producto SaaS en 2026 exige mucho más que crear una interfaz bonita. Los usuarios esperan respuestas instantáneas en cualquier lugar del mundo, los inversores exigen costos operativos predecibles y los desarrolladores no quieren perder fines de semana administrando pods de Kubernetes ni pagando facturas astronómicas por servidores dedicados ociosos.",[321,7384,7385,7386,7388,7389,965,7391,3518,7393,333],{},"En esta guía completa exploramos la arquitectura de ",[330,7387,4900],{},": una base de código lista para producción basada en ",[330,7390,4892],{},[330,7392,6366],{},[330,7394,6369],{},[4903,7396],{},[338,7398,7400],{"id":7399},"por-qué-la-arquitectura-tradicional-de-saas-quedó-obsoleta","¿Por qué la Arquitectura Tradicional de SaaS Quedó Obsoleta?",[321,7402,7403],{},"Durante años, el estándar de la industria para las nuevas startups fue alquilar instancias virtuales (AWS EC2, DigitalOcean) o usar proveedores PaaS con precios agresivos por asiento y por consumo.",[321,7405,7406],{},"Los problemas de este modelo tradicional aparecen rápidamente:",[356,7408,7409,7415,7423],{},[359,7410,7411,7414],{},[330,7412,7413],{},"Cold Starts Críticos",": Las Lambdas tradicionales tardan de 800 ms a 2 s en calentar las conexiones con bases de datos y módulos pesados.",[359,7416,7417,7420,7421,1314],{},[330,7418,7419],{},"Latencia Regional",": Los usuarios de América Latina o Europa sufren con rutas que tienen que llegar a servidores en Estados Unidos (",[325,7422,6398],{},[359,7424,7425,7428],{},[330,7426,7427],{},"Escalado Complejo",": Configurar el autoscaling de contenedores requiere monitorear métricas, límites de memoria y costos que crecen mucho antes que tus ingresos.",[6406,7430,7432],{"id":7431},"la-alternativa-edge-computing-global","La Alternativa: Edge Computing Global",[321,7434,6987,7435,7437],{},[330,7436,6369],{},", tu código JavaScript\u002FTypeScript se ejecuta directamente en la red V8 distribuida en más de 300 ciudades alrededor del mundo:",[424,7439,7440,7446,7451],{},[359,7441,7442,7445],{},[330,7443,7444],{},"Tiempo de arranque de 0 ms"," (cero cold starts).",[359,7447,7448,333],{},[330,7449,7450],{},"Ejecución a milisegundos del usuario final",[359,7452,7453,7456],{},[330,7454,7455],{},"Costo operativo hasta un 80 % menor"," que el de los servidores dedicados.",[343,7458,7460],{"className":4975,"code":7459,"language":4977,"meta":349,"style":349},"\u002F\u002F Ejemplo de endpoint serverless de alto rendimiento en Nuxt 4\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event);\n  \n  \u002F\u002F Se ejecuta en el edge más cercano al usuario, con conexión en pool mediante Hyperdrive\n  const team = await useDatabase().query.teams.findFirst({\n    where: eq(teams.ownerId, user.id)\n  });\n\n  return { team, edgeRegion: event.context.cloudflare?.region ?? \"edge\" };\n});\n",[325,7461,7462,7467,7489,7513,7517,7522,7552,7578,7586,7590,7628],{"__ignoreMap":349},[470,7463,7464],{"class":472,"line":473},[470,7465,7466],{"class":5326},"\u002F\u002F Ejemplo de endpoint serverless de alto rendimiento en Nuxt 4\n",[470,7468,7469,7471,7473,7475,7477,7479,7481,7483,7485,7487],{"class":472,"line":484},[470,7470,5019],{"class":4984},[470,7472,5022],{"class":4984},[470,7474,5950],{"class":5025},[470,7476,5028],{"class":4992},[470,7478,5956],{"class":5955},[470,7480,401],{"class":4988},[470,7482,5962],{"class":5961},[470,7484,5194],{"class":4988},[470,7486,5967],{"class":5222},[470,7488,5131],{"class":4988},[470,7490,7491,7493,7495,7497,7499,7501,7503,7505,7507,7509,7511],{"class":472,"line":509},[470,7492,5974],{"class":5222},[470,7494,4989],{"class":4988},[470,7496,6474],{"class":5226},[470,7498,4996],{"class":4988},[470,7500,5229],{"class":663},[470,7502,5982],{"class":4984},[470,7504,6483],{"class":5025},[470,7506,5028],{"class":5036},[470,7508,5962],{"class":4992},[470,7510,5194],{"class":5036},[470,7512,5009],{"class":4988},[470,7514,7515],{"class":472,"line":528},[470,7516,5998],{"class":5036},[470,7518,7519],{"class":472,"line":5052},[470,7520,7521],{"class":5326},"  \u002F\u002F Se ejecuta en el edge más cercano al usuario, con conexión en pool mediante Hyperdrive\n",[470,7523,7524,7526,7528,7530,7532,7534,7536,7538,7540,7542,7544,7546,7548,7550],{"class":472,"line":5069},[470,7525,5974],{"class":5222},[470,7527,6507],{"class":5226},[470,7529,5229],{"class":663},[470,7531,5982],{"class":4984},[470,7533,6514],{"class":5025},[470,7535,6517],{"class":5036},[470,7537,333],{"class":4988},[470,7539,6522],{"class":4992},[470,7541,333],{"class":4988},[470,7543,5082],{"class":4992},[470,7545,333],{"class":4988},[470,7547,6531],{"class":5025},[470,7549,5028],{"class":5036},[470,7551,5031],{"class":4988},[470,7553,7554,7556,7558,7560,7562,7564,7566,7568,7570,7572,7574,7576],{"class":472,"line":5092},[470,7555,6540],{"class":5036},[470,7557,1149],{"class":4988},[470,7559,6545],{"class":5025},[470,7561,5028],{"class":5036},[470,7563,5082],{"class":4992},[470,7565,333],{"class":4988},[470,7567,6554],{"class":4992},[470,7569,5109],{"class":4988},[470,7571,6474],{"class":4992},[470,7573,333],{"class":4988},[470,7575,6563],{"class":4992},[470,7577,6271],{"class":5036},[470,7579,7580,7582,7584],{"class":472,"line":5123},[470,7581,6108],{"class":4988},[470,7583,5194],{"class":5036},[470,7585,5009],{"class":4988},[470,7587,7588],{"class":472,"line":5134},[470,7589,5014],{"emptyLinePlaceholder":22},[470,7591,7592,7594,7596,7598,7600,7602,7604,7606,7608,7610,7612,7614,7616,7618,7620,7622,7624,7626],{"class":472,"line":5151},[470,7593,6123],{"class":4984},[470,7595,4989],{"class":4988},[470,7597,6507],{"class":4992},[470,7599,5109],{"class":4988},[470,7601,6590],{"class":5036},[470,7603,1149],{"class":4988},[470,7605,6595],{"class":4992},[470,7607,333],{"class":4988},[470,7609,6600],{"class":4992},[470,7611,333],{"class":4988},[470,7613,6223],{"class":4992},[470,7615,6607],{"class":4988},[470,7617,6610],{"class":4992},[470,7619,6613],{"class":663},[470,7621,500],{"class":499},[470,7623,6618],{"class":489},[470,7625,5006],{"class":499},[470,7627,6150],{"class":4988},[470,7629,7630,7632,7634],{"class":472,"line":5168},[470,7631,5191],{"class":4988},[470,7633,5194],{"class":4992},[470,7635,5009],{"class":4988},[4903,7637],{},[338,7639,7641],{"id":7640},"el-monorepo-bun-la-estructura-profesional-de-un-saas-enterprise","El Monorepo Bun: la Estructura Profesional de un SaaS Enterprise",[321,7643,7644,7645,7647,7648,1149],{},"Muchos boilerplates empaquetan el código como un bloque monolítico y desordenado. En ",[330,7646,4900],{}," adoptamos un monorepo orquestado con ",[330,7649,6645],{},[424,7651,7652,7659,7666,7673,7680,7687],{},[359,7653,7654,7658],{},[330,7655,7656],{},[325,7657,6654],{},": El dashboard principal del SaaS, con layouts de equipo, tRPC de extremo a extremo y ajustes de cuenta.",[359,7660,7661,7665],{},[330,7662,7663],{},[325,7664,6662],{},": El portal público de marketing con documentación completa, una landing page de alta conversión y un blog.",[359,7667,7668,7672],{},[330,7669,7670],{},[325,7671,6670],{},": Interfaz estática y ultraseguro para OpenAuth.js, con soporte de magic links e inicios de sesión sociales (Google, GitHub).",[359,7674,7675,7679],{},[330,7676,7677],{},[325,7678,6309],{},": Workers auxiliares de Cloudflare para colas asíncronas, CRON periódico de facturación y WebSockets.",[359,7681,7682,7686],{},[330,7683,7684],{},[325,7685,6685],{},": Módulos de dominio desacoplados (Admin, Billing, Teams, Notifications).",[359,7688,7689,7693,7694,333],{},[330,7690,7691],{},[325,7692,6693],{},": Toda la infraestructura declarada como código TypeScript con ",[330,7695,6366],{},[343,7697,7699],{"className":464,"code":7698,"language":466,"meta":349,"style":349},"# Inicio ultrarrápido en el monorepo Bun\nbun install\nbun run dev\n",[325,7700,7701,7706,7712],{"__ignoreMap":349},[470,7702,7703],{"class":472,"line":473},[470,7704,7705],{"class":5326},"# Inicio ultrarrápido en el monorepo Bun\n",[470,7707,7708,7710],{"class":472,"line":484},[470,7709,1219],{"class":476},[470,7711,1222],{"class":489},[470,7713,7714,7716,7718],{"class":472,"line":509},[470,7715,1219],{"class":476},[470,7717,1229],{"class":489},[470,7719,6721],{"class":489},[4903,7721],{},[338,7723,7725],{"id":7724},"conectando-postgresql-al-edge-con-cloudflare-hyperdrive","Conectando PostgreSQL al Edge con Cloudflare Hyperdrive",[321,7727,7728,7729,7731],{},"El mayor mito sobre las bases de datos relacionales y los Workers era que las conexiones directas agotaban el pool de PostgreSQL. Con ",[330,7730,6733],{},", tus instancias de Postgres se conectan al edge con:",[424,7733,7734,7737,7740],{},[359,7735,7736],{},"Pooling inteligente de conexiones TCP reutilizadas.",[359,7738,7739],{},"Caché de consultas de lectura cerca del usuario.",[359,7741,7742,7743,333],{},"Compatibilidad total con consultas tipadas usando ",[330,7744,6748],{},[343,7746,7747],{"className":4975,"code":6751,"language":4977,"meta":349,"style":349},[325,7748,7749,7769,7785,7789,7809,7839,7861],{"__ignoreMap":349},[470,7750,7751,7753,7755,7757,7759,7761,7763,7765,7767],{"class":472,"line":473},[470,7752,4985],{"class":4984},[470,7754,4989],{"class":4988},[470,7756,6762],{"class":4992},[470,7758,4996],{"class":4988},[470,7760,4999],{"class":4984},[470,7762,500],{"class":499},[470,7764,6771],{"class":489},[470,7766,5006],{"class":499},[470,7768,5009],{"class":4988},[470,7770,7771,7773,7775,7777,7779,7781,7783],{"class":472,"line":484},[470,7772,4985],{"class":4984},[470,7774,6782],{"class":4992},[470,7776,6785],{"class":4984},[470,7778,500],{"class":499},[470,7780,6790],{"class":489},[470,7782,5006],{"class":499},[470,7784,5009],{"class":4988},[470,7786,7787],{"class":472,"line":509},[470,7788,5014],{"emptyLinePlaceholder":22},[470,7790,7791,7793,7795,7797,7799,7801,7803,7805,7807],{"class":472,"line":528},[470,7792,5019],{"class":4984},[470,7794,6805],{"class":5222},[470,7796,6808],{"class":5025},[470,7798,5028],{"class":4988},[470,7800,6813],{"class":5961},[470,7802,1149],{"class":663},[470,7804,6819],{"class":6818},[470,7806,5194],{"class":4988},[470,7808,5131],{"class":4988},[470,7810,7811,7813,7815,7817,7819,7821,7823,7825,7827,7829,7831,7833,7835,7837],{"class":472,"line":5052},[470,7812,5974],{"class":5222},[470,7814,6830],{"class":5226},[470,7816,5229],{"class":663},[470,7818,6835],{"class":5025},[470,7820,5028],{"class":5036},[470,7822,6813],{"class":4992},[470,7824,5109],{"class":4988},[470,7826,4989],{"class":4988},[470,7828,6846],{"class":5036},[470,7830,1149],{"class":4988},[470,7832,6852],{"class":6851},[470,7834,4996],{"class":4988},[470,7836,5194],{"class":5036},[470,7838,5009],{"class":4988},[470,7840,7841,7843,7845,7847,7849,7851,7853,7855,7857,7859],{"class":472,"line":5069},[470,7842,6123],{"class":4984},[470,7844,6762],{"class":5025},[470,7846,5028],{"class":5036},[470,7848,6869],{"class":4992},[470,7850,5109],{"class":4988},[470,7852,4989],{"class":4988},[470,7854,6876],{"class":4992},[470,7856,4996],{"class":4988},[470,7858,5194],{"class":5036},[470,7860,5009],{"class":4988},[470,7862,7863],{"class":472,"line":5092},[470,7864,6887],{"class":4988},[4903,7866],{},[338,7868,7870],{"id":7869},"empieza-con-nuxfire-ahorra-meses-de-desarrollo","Empieza con Nuxfire: Ahorra Meses de Desarrollo",[321,7872,7873,7874,7876],{},"En lugar de gastar de 3 a 6 meses configurando desde cero la autenticación, los WebSockets, las colas en segundo plano, la facturación con Stripe y los permisos multi-tenant, ",[330,7875,4900],{}," entrega todo ese código listo, probado y con tipado estricto de extremo a extremo.",[424,7878,7879,7885,7891],{},[359,7880,7881,7884],{},[330,7882,7883],{},"Acceso Completo al Código Fuente",": Sin paquetes privados cerrados ni mensualidades por desarrollador.",[359,7886,7887,7890],{},[330,7888,7889],{},"Entrega Inmediata",": Obtén acceso inmediato al repositorio y al Grupo VIP oficial justo después de la confirmación.",[359,7892,7893,7896],{},[330,7894,7895],{},"Licencia Perpetua",": Crea proyectos comerciales ilimitados.",[321,7898,7899],{},[455,7900,7901],{"href":5385},[330,7902,7903],{},"Asegura el Acceso Inmediato al Código Fuente al Precio Promocional →",[1945,7905,6929],{},{"title":349,"searchDepth":484,"depth":484,"links":7907},[7908,7911,7912,7913],{"id":7399,"depth":484,"text":7400,"children":7909},[7910],{"id":7431,"depth":509,"text":7432},{"id":7640,"depth":484,"text":7641},{"id":7724,"depth":484,"text":7725},{"id":7869,"depth":484,"text":7870},"Arquitectura Edge","Aprende a estructurar un SaaS moderno en 2026 con Nuxt 4, SST v4 y Cloudflare Workers. Cold starts cero, latencia global inferior a 50 ms y un enorme ahorro de infraestructura.","\u002Fimages\u002Fblog\u002Fnuxt4-cloudflare-saas-es.svg",{},"\u002Fimages\u002Fblog\u002Fnuxt4-cloudflare-saas-es.png","\u002Fblog\u002Fcomo-construir-saas-escalable-nuxt-4-cloudflare",{"title":7377,"description":7915},"blog\u002Fcomo-construir-saas-escalable-nuxt-4-cloudflare",[4892,6369,6947,6948,6366],{"en":6950,"pt-br":6951,"es":6952},"VIus77mILU0o6hBZjaayHtRfaOzMLUwkEk1ItSiOK80",{"id":7926,"title":7927,"author":315,"authorType":316,"body":7928,"category":8463,"date":5401,"description":8464,"extension":1971,"image":8465,"lang":1973,"meta":8466,"navigation":22,"ogImage":8467,"path":8468,"readTime":6336,"seo":8469,"stem":8470,"tags":8471,"translations":8472,"updated":316,"__hash__":8473},"blog\u002Fblog\u002Fcomo-construir-saas-escalavel-nuxt-4-cloudflare.md","Como Construir um SaaS Escalável com Nuxt 4 e Cloudflare Workers: O Guia Definitivo",{"type":318,"value":7929,"toc":8455},[7930,7933,7945,7947,7951,7954,7957,7978,7982,7988,8006,8185,8187,8191,8199,8245,8269,8271,8275,8281,8294,8414,8416,8420,8426,8446,8453],[321,7931,7932],{},"Lançar um produto SaaS em 2026 exige muito mais do que apenas criar uma interface bonita. Usuários esperam respostas instantâneas em qualquer lugar do mundo, investidores exigem custos operacionais previsíveis e desenvolvedores não querem perder fins de semana gerenciando pods do Kubernetes ou pagando contas astronômicas de servidores dedicados ociosos.",[321,7934,7935,7936,7938,7939,965,7941,415,7943,333],{},"Neste guia completo, exploramos a arquitetura do ",[330,7937,4900],{},": uma base de código pronta para produção baseada em ",[330,7940,4892],{},[330,7942,6366],{},[330,7944,6369],{},[4903,7946],{},[338,7948,7950],{"id":7949},"por-que-a-arquitetura-tradicional-de-saas-ficou-obsoleta","Por que a Arquitetura Tradicional de SaaS Ficou Obsoleta?",[321,7952,7953],{},"Durante anos, o padrão da indústria para novas startups foi alugar instâncias virtuais (AWS EC2, DigitalOcean) ou usar provedores PaaS com precificação agressiva por assento e consumo.",[321,7955,7956],{},"Os problemas desse modelo tradicional aparecem rapidamente:",[356,7958,7959,7964,7972],{},[359,7960,7961,7963],{},[330,7962,7413],{},": Lambdas tradicionais demoram de 800ms a 2s para aquecer conexões com bancos e módulos pesados.",[359,7965,7966,7969,7970,1314],{},[330,7967,7968],{},"Latência Regional",": Usuários na América Latina ou Europa sofrem com rotas que precisam bater em servidores nos Estados Unidos (",[325,7971,6398],{},[359,7973,7974,7977],{},[330,7975,7976],{},"Escalonamento Complexo",": Configurar autoscaling de containers requer monitoramento de métricas, limites de memória e custos que escalam muito antes da sua receita.",[6406,7979,7981],{"id":7980},"a-alternativa-edge-computing-global","A Alternativa: Edge Computing Global",[321,7983,7984,7985,7987],{},"Com os ",[330,7986,6369],{},", seu código JavaScript\u002FTypeScript roda diretamente na rede V8 distribuída em mais de 300 cidades ao redor do globo:",[424,7989,7990,7995,8000],{},[359,7991,7992,6422],{},[330,7993,7994],{},"Tempo de inicialização de 0ms",[359,7996,7997,333],{},[330,7998,7999],{},"Execução a milissegundos do usuário final",[359,8001,8002,8005],{},[330,8003,8004],{},"Custo operacional até 80% menor"," que servidores dedicados.",[343,8007,8009],{"className":4975,"code":8008,"language":4977,"meta":349,"style":349},"\u002F\u002F Exemplo de endpoint serverless de alta performance no Nuxt 4\nexport default defineEventHandler(async (event) => {\n  const { user } = await requireUserSession(event);\n  \n  \u002F\u002F Executado na borda mais próxima do usuário com conexão em pool via Hyperdrive\n  const team = await useDatabase().query.teams.findFirst({\n    where: eq(teams.ownerId, user.id)\n  });\n\n  return { team, edgeRegion: event.context.cloudflare?.region ?? \"edge\" };\n});\n",[325,8010,8011,8016,8038,8062,8066,8071,8101,8127,8135,8139,8177],{"__ignoreMap":349},[470,8012,8013],{"class":472,"line":473},[470,8014,8015],{"class":5326},"\u002F\u002F Exemplo de endpoint serverless de alta performance no Nuxt 4\n",[470,8017,8018,8020,8022,8024,8026,8028,8030,8032,8034,8036],{"class":472,"line":484},[470,8019,5019],{"class":4984},[470,8021,5022],{"class":4984},[470,8023,5950],{"class":5025},[470,8025,5028],{"class":4992},[470,8027,5956],{"class":5955},[470,8029,401],{"class":4988},[470,8031,5962],{"class":5961},[470,8033,5194],{"class":4988},[470,8035,5967],{"class":5222},[470,8037,5131],{"class":4988},[470,8039,8040,8042,8044,8046,8048,8050,8052,8054,8056,8058,8060],{"class":472,"line":509},[470,8041,5974],{"class":5222},[470,8043,4989],{"class":4988},[470,8045,6474],{"class":5226},[470,8047,4996],{"class":4988},[470,8049,5229],{"class":663},[470,8051,5982],{"class":4984},[470,8053,6483],{"class":5025},[470,8055,5028],{"class":5036},[470,8057,5962],{"class":4992},[470,8059,5194],{"class":5036},[470,8061,5009],{"class":4988},[470,8063,8064],{"class":472,"line":528},[470,8065,5998],{"class":5036},[470,8067,8068],{"class":472,"line":5052},[470,8069,8070],{"class":5326},"  \u002F\u002F Executado na borda mais próxima do usuário com conexão em pool via Hyperdrive\n",[470,8072,8073,8075,8077,8079,8081,8083,8085,8087,8089,8091,8093,8095,8097,8099],{"class":472,"line":5069},[470,8074,5974],{"class":5222},[470,8076,6507],{"class":5226},[470,8078,5229],{"class":663},[470,8080,5982],{"class":4984},[470,8082,6514],{"class":5025},[470,8084,6517],{"class":5036},[470,8086,333],{"class":4988},[470,8088,6522],{"class":4992},[470,8090,333],{"class":4988},[470,8092,5082],{"class":4992},[470,8094,333],{"class":4988},[470,8096,6531],{"class":5025},[470,8098,5028],{"class":5036},[470,8100,5031],{"class":4988},[470,8102,8103,8105,8107,8109,8111,8113,8115,8117,8119,8121,8123,8125],{"class":472,"line":5092},[470,8104,6540],{"class":5036},[470,8106,1149],{"class":4988},[470,8108,6545],{"class":5025},[470,8110,5028],{"class":5036},[470,8112,5082],{"class":4992},[470,8114,333],{"class":4988},[470,8116,6554],{"class":4992},[470,8118,5109],{"class":4988},[470,8120,6474],{"class":4992},[470,8122,333],{"class":4988},[470,8124,6563],{"class":4992},[470,8126,6271],{"class":5036},[470,8128,8129,8131,8133],{"class":472,"line":5123},[470,8130,6108],{"class":4988},[470,8132,5194],{"class":5036},[470,8134,5009],{"class":4988},[470,8136,8137],{"class":472,"line":5134},[470,8138,5014],{"emptyLinePlaceholder":22},[470,8140,8141,8143,8145,8147,8149,8151,8153,8155,8157,8159,8161,8163,8165,8167,8169,8171,8173,8175],{"class":472,"line":5151},[470,8142,6123],{"class":4984},[470,8144,4989],{"class":4988},[470,8146,6507],{"class":4992},[470,8148,5109],{"class":4988},[470,8150,6590],{"class":5036},[470,8152,1149],{"class":4988},[470,8154,6595],{"class":4992},[470,8156,333],{"class":4988},[470,8158,6600],{"class":4992},[470,8160,333],{"class":4988},[470,8162,6223],{"class":4992},[470,8164,6607],{"class":4988},[470,8166,6610],{"class":4992},[470,8168,6613],{"class":663},[470,8170,500],{"class":499},[470,8172,6618],{"class":489},[470,8174,5006],{"class":499},[470,8176,6150],{"class":4988},[470,8178,8179,8181,8183],{"class":472,"line":5168},[470,8180,5191],{"class":4988},[470,8182,5194],{"class":4992},[470,8184,5009],{"class":4988},[4903,8186],{},[338,8188,8190],{"id":8189},"o-monorepo-bun-estrutura-profissional-de-um-saas-enterprise","O Monorepo Bun: Estrutura Profissional de um SaaS Enterprise",[321,8192,8193,8194,8196,8197,1149],{},"Muitos boilerplates empacotam código como um bloco monolítico e desordenado. No ",[330,8195,4900],{},", adotamos um monorepo orquestrado com ",[330,8198,6645],{},[424,8200,8201,8208,8215,8222,8229,8236],{},[359,8202,8203,8207],{},[330,8204,8205],{},[325,8206,6654],{},": O dashboard principal do SaaS, com layouts de equipe, tRPC ponta a ponta e configurações de conta.",[359,8209,8210,8214],{},[330,8211,8212],{},[325,8213,6662],{},": O portal público de marketing com documentação completa, landing page de alta conversão e blog.",[359,8216,8217,8221],{},[330,8218,8219],{},[325,8220,6670],{},": Interface estática e ultra-segura para OpenAuth.js, suportando magic links e logins sociais (Google, GitHub).",[359,8223,8224,8228],{},[330,8225,8226],{},[325,8227,6309],{},": Workers auxiliares da Cloudflare para filas assíncronas, CRON periódico de faturamento e WebSockets.",[359,8230,8231,8235],{},[330,8232,8233],{},[325,8234,6685],{},": Módulos desacoplados de domínio (Admin, Billing, Teams, Notifications).",[359,8237,8238,8242,8243,333],{},[330,8239,8240],{},[325,8241,6693],{},": Toda a infraestrutura declarada como código TypeScript usando ",[330,8244,6366],{},[343,8246,8248],{"className":464,"code":8247,"language":466,"meta":349,"style":349},"# Inicialização ultra-rápida no Bun Monorepo\nbun install\nbun run dev\n",[325,8249,8250,8255,8261],{"__ignoreMap":349},[470,8251,8252],{"class":472,"line":473},[470,8253,8254],{"class":5326},"# Inicialização ultra-rápida no Bun Monorepo\n",[470,8256,8257,8259],{"class":472,"line":484},[470,8258,1219],{"class":476},[470,8260,1222],{"class":489},[470,8262,8263,8265,8267],{"class":472,"line":509},[470,8264,1219],{"class":476},[470,8266,1229],{"class":489},[470,8268,6721],{"class":489},[4903,8270],{},[338,8272,8274],{"id":8273},"conectando-postgresql-à-edge-com-cloudflare-hyperdrive","Conectando PostgreSQL à Edge com Cloudflare Hyperdrive",[321,8276,8277,8278,8280],{},"O maior mito sobre bancos de dados relacionais e Workers era que conexões diretas esgotavam o pool do PostgreSQL. Com o ",[330,8279,6733],{},", suas instâncias Postgres conectam-se à borda com:",[424,8282,8283,8286,8289],{},[359,8284,8285],{},"Pooling inteligente de conexões TCP reutilizadas.",[359,8287,8288],{},"Cache de consultas de leitura próximo ao usuário.",[359,8290,8291,8292,333],{},"Compatibilidade total com queries tipadas usando ",[330,8293,6748],{},[343,8295,8296],{"className":4975,"code":6751,"language":4977,"meta":349,"style":349},[325,8297,8298,8318,8334,8338,8358,8388,8410],{"__ignoreMap":349},[470,8299,8300,8302,8304,8306,8308,8310,8312,8314,8316],{"class":472,"line":473},[470,8301,4985],{"class":4984},[470,8303,4989],{"class":4988},[470,8305,6762],{"class":4992},[470,8307,4996],{"class":4988},[470,8309,4999],{"class":4984},[470,8311,500],{"class":499},[470,8313,6771],{"class":489},[470,8315,5006],{"class":499},[470,8317,5009],{"class":4988},[470,8319,8320,8322,8324,8326,8328,8330,8332],{"class":472,"line":484},[470,8321,4985],{"class":4984},[470,8323,6782],{"class":4992},[470,8325,6785],{"class":4984},[470,8327,500],{"class":499},[470,8329,6790],{"class":489},[470,8331,5006],{"class":499},[470,8333,5009],{"class":4988},[470,8335,8336],{"class":472,"line":509},[470,8337,5014],{"emptyLinePlaceholder":22},[470,8339,8340,8342,8344,8346,8348,8350,8352,8354,8356],{"class":472,"line":528},[470,8341,5019],{"class":4984},[470,8343,6805],{"class":5222},[470,8345,6808],{"class":5025},[470,8347,5028],{"class":4988},[470,8349,6813],{"class":5961},[470,8351,1149],{"class":663},[470,8353,6819],{"class":6818},[470,8355,5194],{"class":4988},[470,8357,5131],{"class":4988},[470,8359,8360,8362,8364,8366,8368,8370,8372,8374,8376,8378,8380,8382,8384,8386],{"class":472,"line":5052},[470,8361,5974],{"class":5222},[470,8363,6830],{"class":5226},[470,8365,5229],{"class":663},[470,8367,6835],{"class":5025},[470,8369,5028],{"class":5036},[470,8371,6813],{"class":4992},[470,8373,5109],{"class":4988},[470,8375,4989],{"class":4988},[470,8377,6846],{"class":5036},[470,8379,1149],{"class":4988},[470,8381,6852],{"class":6851},[470,8383,4996],{"class":4988},[470,8385,5194],{"class":5036},[470,8387,5009],{"class":4988},[470,8389,8390,8392,8394,8396,8398,8400,8402,8404,8406,8408],{"class":472,"line":5069},[470,8391,6123],{"class":4984},[470,8393,6762],{"class":5025},[470,8395,5028],{"class":5036},[470,8397,6869],{"class":4992},[470,8399,5109],{"class":4988},[470,8401,4989],{"class":4988},[470,8403,6876],{"class":4992},[470,8405,4996],{"class":4988},[470,8407,5194],{"class":5036},[470,8409,5009],{"class":4988},[470,8411,8412],{"class":472,"line":5092},[470,8413,6887],{"class":4988},[4903,8415],{},[338,8417,8419],{"id":8418},"comece-com-o-nuxfire-economize-meses-de-desenvolvimento","Comece com o Nuxfire: Economize Meses de Desenvolvimento",[321,8421,8422,8423,8425],{},"Em vez de gastar de 3 a 6 meses configurando autenticação, WebSockets, filas de background, faturamento Stripe e permissões multi-tenant do zero, o ",[330,8424,4900],{}," entrega todo esse código pronto, testado e com tipagem estrita de ponta a ponta.",[424,8427,8428,8434,8440],{},[359,8429,8430,8433],{},[330,8431,8432],{},"Acesso Completo ao Código-Fonte",": Sem pacotes privados fechados ou mensalidades por desenvolvedor.",[359,8435,8436,8439],{},[330,8437,8438],{},"Entrega Imediata",": Tenha acesso imediato ao repositório e ao Grupo VIP oficial logo após a confirmação.",[359,8441,8442,8445],{},[330,8443,8444],{},"Licença Perpétua",": Crie projetos comerciais ilimitados.",[321,8447,8448],{},[455,8449,8450],{"href":5844},[330,8451,8452],{},"Garantir Acesso Imediato ao Código-Fonte no Preço Promocional →",[1945,8454,6929],{},{"title":349,"searchDepth":484,"depth":484,"links":8456},[8457,8460,8461,8462],{"id":7949,"depth":484,"text":7950,"children":8458},[8459],{"id":7980,"depth":509,"text":7981},{"id":8189,"depth":484,"text":8190},{"id":8273,"depth":484,"text":8274},{"id":8418,"depth":484,"text":8419},"Arquitetura Edge","Aprenda a estruturar um SaaS moderno em 2026 com Nuxt 4, SST v4 e Cloudflare Workers. Cold starts zero, latência global sub-50ms e economia massiva de infraestrutura.","\u002Fimages\u002Fblog\u002Fnuxt4-cloudflare-saas.svg",{},"\u002Fimages\u002Fblog\u002Fnuxt4-cloudflare-saas.png","\u002Fblog\u002Fcomo-construir-saas-escalavel-nuxt-4-cloudflare",{"title":7927,"description":8464},"blog\u002Fcomo-construir-saas-escalavel-nuxt-4-cloudflare",[4892,6369,6947,6948,6366],{"en":6950,"pt-br":6951,"es":6952},"_J1faUFvy4qSb8nP80-QweYUK3Oy0-5U4MYznCU_qrQ",{"id":8475,"title":8476,"author":315,"authorType":316,"body":8477,"category":8879,"date":5401,"description":8880,"extension":1971,"image":8881,"lang":1973,"meta":8882,"navigation":22,"ogImage":8883,"path":8884,"readTime":6336,"seo":8885,"stem":8886,"tags":8887,"translations":8889,"updated":316,"__hash__":8890},"blog\u002Fblog\u002Ffilas-assincronas-jobs-segundo-plano-serverless.md","Processamento Assíncrono no Serverless: Cloudflare Queues e Dead Letter Queues",{"type":318,"value":8478,"toc":8874},[8479,8482,8485,8495,8497,8501,8504,8510,8532,8724,8726,8730,8733,8736,8755,8833,8835,8839,8842,8853,8862,8865,8872],[321,8480,8481],{},"No desenvolvimento de aplicações web modernas, nada degrada mais a experiência do usuário do que telas travadas esperando operações demoradas.",[321,8483,8484],{},"Seja aguardando uma resposta de 15 segundos de um modelo de linguagem (LLM como Claude ou GPT-4), gerando um relatório em PDF ou disparando centenas de e-mails transacionais, processar tarefas pesadas de forma síncrona dentro da requisição HTTP é uma receita garantida para timeouts e falhas de conexão.",[321,8486,8487,8488,8490,8491,415,8493,333],{},"No ",[330,8489,4900],{},", todo o processamento em segundo plano é gerenciado de forma resiliente por ",[330,8492,5889],{},[330,8494,5892],{},[4903,8496],{},[338,8498,8500],{"id":8499},"a-arquitetura-produtor-consumidor-serverless","A Arquitetura Produtor-Consumidor Serverless",[321,8502,8503],{},"Em arquiteturas tradicionais, configurar filas exigia hospedar instâncias RabbitMQ ou gerenciar filas Redis BullMQ com workers Node.js monitorados por PM2.",[321,8505,8506,8507,8509],{},"Com a ",[330,8508,5889],{},", a infraestrutura é 100% serverless:",[356,8511,8512,8521,8527],{},[359,8513,8514,8517,8518,8520],{},[330,8515,8516],{},"Produtor",": O dashboard ou API enfileira mensagens em milissegundos e devolve uma resposta imediata (",[325,8519,5918],{},") para o usuário.",[359,8522,8523,8526],{},[330,8524,8525],{},"Fila (Queue)",": A Cloudflare armazena as mensagens com garantia de entrega e controle de concorrência.",[359,8528,8529,8531],{},[330,8530,7012],{},": Um worker dedicado processa os lotes de mensagens em segundo plano com retry automático.",[343,8533,8535],{"className":4975,"code":8534,"language":4977,"meta":349,"style":349},"\u002F\u002F Enfileirando um job assíncrono a partir de uma API Nuxt 4\nexport default defineEventHandler(async (event) => {\n  const body = await readBody(event);\n  \n  \u002F\u002F Publica na fila da Cloudflare em menos de 10ms\n  await env.APP_QUEUE.send({\n    type: \"PROCESS_AI_COMPLETION\",\n    payload: {\n      documentId: body.documentId,\n      prompt: body.prompt\n    },\n    enqueuedAt: Date.now()\n  });\n\n  return { success: true, message: \"Processamento iniciado em segundo plano.\" };\n});\n",[325,8536,8537,8542,8564,8584,8588,8593,8611,8625,8633,8647,8659,8663,8677,8685,8689,8716],{"__ignoreMap":349},[470,8538,8539],{"class":472,"line":473},[470,8540,8541],{"class":5326},"\u002F\u002F Enfileirando um job assíncrono a partir de uma API Nuxt 4\n",[470,8543,8544,8546,8548,8550,8552,8554,8556,8558,8560,8562],{"class":472,"line":484},[470,8545,5019],{"class":4984},[470,8547,5022],{"class":4984},[470,8549,5950],{"class":5025},[470,8551,5028],{"class":4992},[470,8553,5956],{"class":5955},[470,8555,401],{"class":4988},[470,8557,5962],{"class":5961},[470,8559,5194],{"class":4988},[470,8561,5967],{"class":5222},[470,8563,5131],{"class":4988},[470,8565,8566,8568,8570,8572,8574,8576,8578,8580,8582],{"class":472,"line":509},[470,8567,5974],{"class":5222},[470,8569,5977],{"class":5226},[470,8571,5229],{"class":663},[470,8573,5982],{"class":4984},[470,8575,5985],{"class":5025},[470,8577,5028],{"class":5036},[470,8579,5962],{"class":4992},[470,8581,5194],{"class":5036},[470,8583,5009],{"class":4988},[470,8585,8586],{"class":472,"line":528},[470,8587,5998],{"class":5036},[470,8589,8590],{"class":472,"line":5052},[470,8591,8592],{"class":5326},"  \u002F\u002F Publica na fila da Cloudflare em menos de 10ms\n",[470,8594,8595,8597,8599,8601,8603,8605,8607,8609],{"class":472,"line":5069},[470,8596,6008],{"class":4984},[470,8598,6011],{"class":4992},[470,8600,333],{"class":4988},[470,8602,6016],{"class":5226},[470,8604,333],{"class":4988},[470,8606,6021],{"class":5025},[470,8608,5028],{"class":5036},[470,8610,5031],{"class":4988},[470,8612,8613,8615,8617,8619,8621,8623],{"class":472,"line":5092},[470,8614,6030],{"class":5036},[470,8616,1149],{"class":4988},[470,8618,500],{"class":499},[470,8620,6037],{"class":489},[470,8622,5006],{"class":499},[470,8624,5049],{"class":4988},[470,8626,8627,8629,8631],{"class":472,"line":5123},[470,8628,6046],{"class":5036},[470,8630,1149],{"class":4988},[470,8632,5131],{"class":4988},[470,8634,8635,8637,8639,8641,8643,8645],{"class":472,"line":5134},[470,8636,6055],{"class":5036},[470,8638,1149],{"class":4988},[470,8640,5977],{"class":4992},[470,8642,333],{"class":4988},[470,8644,6064],{"class":4992},[470,8646,5049],{"class":4988},[470,8648,8649,8651,8653,8655,8657],{"class":472,"line":5151},[470,8650,6071],{"class":5036},[470,8652,1149],{"class":4988},[470,8654,5977],{"class":4992},[470,8656,333],{"class":4988},[470,8658,6080],{"class":4992},[470,8660,8661],{"class":472,"line":5168},[470,8662,6085],{"class":4988},[470,8664,8665,8667,8669,8671,8673,8675],{"class":472,"line":5182},[470,8666,6090],{"class":5036},[470,8668,1149],{"class":4988},[470,8670,6095],{"class":4992},[470,8672,333],{"class":4988},[470,8674,6100],{"class":5025},[470,8676,6103],{"class":5036},[470,8678,8679,8681,8683],{"class":472,"line":5188},[470,8680,6108],{"class":4988},[470,8682,5194],{"class":5036},[470,8684,5009],{"class":4988},[470,8686,8687],{"class":472,"line":6115},[470,8688,5014],{"emptyLinePlaceholder":22},[470,8690,8691,8693,8695,8697,8699,8701,8703,8705,8707,8709,8712,8714],{"class":472,"line":6120},[470,8692,6123],{"class":4984},[470,8694,4989],{"class":4988},[470,8696,6128],{"class":5036},[470,8698,1149],{"class":4988},[470,8700,6133],{"class":5320},[470,8702,5109],{"class":4988},[470,8704,6138],{"class":5036},[470,8706,1149],{"class":4988},[470,8708,500],{"class":499},[470,8710,8711],{"class":489},"Processamento iniciado em segundo plano.",[470,8713,5006],{"class":499},[470,8715,6150],{"class":4988},[470,8717,8718,8720,8722],{"class":472,"line":6153},[470,8719,5191],{"class":4988},[470,8721,5194],{"class":4992},[470,8723,5009],{"class":4988},[4903,8725],{},[338,8727,8729],{"id":8728},"resiliência-com-backoff-exponencial-e-dead-letter-queue-dlq","Resiliência com Backoff Exponencial e Dead Letter Queue (DLQ)",[321,8731,8732],{},"E se uma API externa de IA ou um provedor de e-mail estiver temporariamente fora do ar?",[321,8734,8735],{},"No Nuxfire, as filas são configuradas no SST com política estrita de tolerância a falhas:",[424,8737,8738,8744,8749],{},[359,8739,8740,8743],{},[330,8741,8742],{},"Tentativas automáticas",": O consumidor tenta reprocessar a mensagem até 3 vezes com intervalos graduais (Backoff Exponencial).",[359,8745,8746,8748],{},[330,8747,6184],{},": Caso a falha persista após as tentativas máximas, a mensagem não é perdida! Ela é enviada automaticamente para uma fila de quarentena (DLQ).",[359,8750,8751,8754],{},[330,8752,8753],{},"Inspeção e Alerta",": A equipe de suporte pode inspecionar mensagens na DLQ, corrigir o problema de integração e reenfileirar as mensagens com segurança.",[343,8756,8757],{"className":4975,"code":7239,"language":4977,"meta":349,"style":349},[325,8758,8759,8763,8797,8825],{"__ignoreMap":349},[470,8760,8761],{"class":472,"line":473},[470,8762,7246],{"class":5326},[470,8764,8765,8767,8769,8771,8773,8775,8777,8779,8781,8783,8785,8787,8789,8791,8793,8795],{"class":472,"line":484},[470,8766,5019],{"class":4984},[470,8768,5223],{"class":5222},[470,8770,6210],{"class":5226},[470,8772,5229],{"class":663},[470,8774,6215],{"class":663},[470,8776,6218],{"class":4992},[470,8778,333],{"class":4988},[470,8780,6223],{"class":4992},[470,8782,333],{"class":4988},[470,8784,5924],{"class":5025},[470,8786,5028],{"class":4992},[470,8788,5006],{"class":499},[470,8790,6234],{"class":489},[470,8792,5006],{"class":499},[470,8794,5109],{"class":4988},[470,8796,5131],{"class":4988},[470,8798,8799,8801,8803,8805,8807,8809,8811,8813,8815,8817,8819,8821,8823],{"class":472,"line":509},[470,8800,6245],{"class":5036},[470,8802,1149],{"class":4988},[470,8804,6215],{"class":663},[470,8806,6218],{"class":4992},[470,8808,333],{"class":4988},[470,8810,6223],{"class":4992},[470,8812,333],{"class":4988},[470,8814,5924],{"class":5025},[470,8816,5028],{"class":4992},[470,8818,5006],{"class":499},[470,8820,6266],{"class":489},[470,8822,5006],{"class":499},[470,8824,6271],{"class":4992},[470,8826,8827,8829,8831],{"class":472,"line":528},[470,8828,5191],{"class":4988},[470,8830,5194],{"class":4992},[470,8832,5009],{"class":4988},[4903,8834],{},[338,8836,8838],{"id":8837},"tarefas-periódicas-com-o-cron-worker","Tarefas Periódicas com o CRON Worker",[321,8840,8841],{},"Além de filas orientadas a eventos, muitos SaaS precisam de rotinas agendadas:",[424,8843,8844,8847,8850],{},[359,8845,8846],{},"Verificar faturas vencidas no Stripe.",[359,8848,8849],{},"Limpar tokens de convite expirados.",[359,8851,8852],{},"Consolidar métricas de uso diário de tokens de IA.",[321,8854,8855,8856,8858,8859,8861],{},"O Nuxfire inclui um ",[330,8857,6305],{}," nativo configurado em ",[325,8860,6309],{},", disparado a cada hora sem depender de serviços externos como Cronhook ou instâncias de cron no Linux.",[321,8863,8864],{},"Construa sistemas distribuídos profissionais sem surpresas operacionais.",[321,8866,8867],{},[455,8868,8869],{"href":5844},[330,8870,8871],{},"Acesse o monorepo do Nuxfire agora mesmo com entrega imediata e suporte no Grupo VIP →",[1945,8873,6324],{},{"title":349,"searchDepth":484,"depth":484,"links":8875},[8876,8877,8878],{"id":8499,"depth":484,"text":8500},{"id":8728,"depth":484,"text":8729},{"id":8837,"depth":484,"text":8838},"Sistemas Distribuídos","Como executar tarefas demoradas, chamadas de IA e disparos de e-mails em lote sem travar a interface e sem estourar timeouts HTTP na Cloudflare.","\u002Fimages\u002Fblog\u002Fcloudflare-queues-background-jobs.svg",{},"\u002Fimages\u002Fblog\u002Fcloudflare-queues-background-jobs.png","\u002Fblog\u002Ffilas-assincronas-jobs-segundo-plano-serverless",{"title":8476,"description":8880},"blog\u002Ffilas-assincronas-jobs-segundo-plano-serverless",[5889,6340,6341,8888],"Assíncrono",{"en":6344,"pt-br":6345,"es":6346},"ALnBVyGEU1HWOJqtWnCvfDPA-UADFJHhGUiCfzV-oak",{"id":8892,"title":8893,"author":8894,"authorType":8895,"body":8896,"category":8902,"date":5401,"description":9662,"extension":1971,"image":9663,"lang":1973,"meta":9664,"navigation":22,"ogImage":9665,"path":9666,"readTime":9667,"seo":9668,"stem":9669,"tags":316,"translations":9670,"updated":316,"__hash__":9674},"blog\u002Fblog\u002Fgetting-started-with-typescript.md","Primeiros Passos com TypeScript no Nuxt 4: Guia Completo para SaaS","Adilson Noveli","Person",{"type":318,"value":8897,"toc":9656},[8898,8904,8910,8912,8916,8919,8949,8951,8955,8958,9223,9479,9629,9631,9635,9638,9640,9644,9647,9653],[321,8899,680,8900,8903],{},[330,8901,8902],{},"TypeScript"," tornou-se um requisito indispensável no desenvolvimento moderno de software, especialmente na construção de aplicações SaaS críticas onde erros em tempo de execução custam clientes e reputação.",[321,8905,8906,8907,8909],{},"No ecossistema do ",[330,8908,4900],{},", o TypeScript não é apenas um linter ou uma checagem superficial de tipos: ele atua como o alicerce fundamental que conecta o banco de dados PostgreSQL ao cliente Vue 3, passando pela camada de APIs com tRPC e pela infraestrutura com SST v4.",[4903,8911],{},[338,8913,8915],{"id":8914},"por-que-tipagem-estrita-é-crítica-em-saas","Por que Tipagem Estrita é Crítica em SaaS?",[321,8917,8918],{},"Construir um software escalável exige previsibilidade. Quando múltiplos desenvolvedores (ou assistentes de IA como Cursor e Claude Code) colaboram no mesmo código, a tipagem estrita garante:",[356,8920,8921,8927,8937,8943],{},[359,8922,8923,8926],{},[330,8924,8925],{},"Refatorações sem Medo",": Renomeie campos no banco ou mude assinaturas de funções e o compilador apontará exatamente quais telas precisam de ajuste.",[359,8928,8929,8932,8933,8936],{},[330,8930,8931],{},"Eliminação de Classes Inteiras de Bugs",": ",[325,8934,8935],{},"undefined is not a function"," e acessos a propriedades inexistentes são interceptados antes do commit.",[359,8938,8939,8942],{},[330,8940,8941],{},"Autocompletação Inteligente na IDE",": Produtividade multiplicada com sugestão instantânea de parâmetros, retornos e tipos utilitários.",[359,8944,8945,8948],{},[330,8946,8947],{},"Alinhamento Perfeito com Ferramentas de IA",": LLMs produzem código substancialmente melhor quando guiados por definições estritas de interfaces.",[4903,8950],{},[338,8952,8954],{"id":8953},"o-fluxo-de-tipagem-ponta-a-ponta-no-nuxfire","O Fluxo de Tipagem Ponta a Ponta no Nuxfire",[321,8956,8957],{},"A arquitetura do Nuxfire implementa uma esteira completa de inferência de tipos em 3 camadas:",[343,8959,8961],{"className":4975,"code":8960,"language":4977,"meta":349,"style":349},"\u002F\u002F 1. Schema do Banco de Dados (Drizzle ORM)\nexport const users = pgTable(\"users\", {\n  id: text(\"id\").primaryKey(),\n  email: text(\"email\").notNull().unique(),\n  role: text(\"role\", { enum: [\"owner\", \"admin\", \"member\"] }).notNull(),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n});\n\nexport type User = typeof users.$inferSelect;\nexport type NewUser = typeof users.$inferInsert;\n",[325,8962,8963,8968,8995,9024,9060,9128,9164,9172,9176,9201],{"__ignoreMap":349},[470,8964,8965],{"class":472,"line":473},[470,8966,8967],{"class":5326},"\u002F\u002F 1. Schema do Banco de Dados (Drizzle ORM)\n",[470,8969,8970,8972,8974,8977,8979,8982,8984,8986,8989,8991,8993],{"class":472,"line":484},[470,8971,5019],{"class":4984},[470,8973,5223],{"class":5222},[470,8975,8976],{"class":5226}," users",[470,8978,5229],{"class":663},[470,8980,8981],{"class":5025}," pgTable",[470,8983,5028],{"class":4992},[470,8985,5006],{"class":499},[470,8987,8988],{"class":489},"users",[470,8990,5006],{"class":499},[470,8992,5109],{"class":4988},[470,8994,5131],{"class":4988},[470,8996,8997,9000,9002,9005,9007,9009,9011,9013,9015,9017,9020,9022],{"class":472,"line":509},[470,8998,8999],{"class":5036},"  id",[470,9001,1149],{"class":4988},[470,9003,9004],{"class":5025}," text",[470,9006,5028],{"class":4992},[470,9008,5006],{"class":499},[470,9010,6563],{"class":489},[470,9012,5006],{"class":499},[470,9014,5194],{"class":4992},[470,9016,333],{"class":4988},[470,9018,9019],{"class":5025},"primaryKey",[470,9021,6517],{"class":4992},[470,9023,5049],{"class":4988},[470,9025,9026,9029,9031,9033,9035,9037,9040,9042,9044,9046,9049,9051,9053,9056,9058],{"class":472,"line":528},[470,9027,9028],{"class":5036},"  email",[470,9030,1149],{"class":4988},[470,9032,9004],{"class":5025},[470,9034,5028],{"class":4992},[470,9036,5006],{"class":499},[470,9038,9039],{"class":489},"email",[470,9041,5006],{"class":499},[470,9043,5194],{"class":4992},[470,9045,333],{"class":4988},[470,9047,9048],{"class":5025},"notNull",[470,9050,6517],{"class":4992},[470,9052,333],{"class":4988},[470,9054,9055],{"class":5025},"unique",[470,9057,6517],{"class":4992},[470,9059,5049],{"class":4988},[470,9061,9062,9065,9067,9069,9071,9073,9076,9078,9080,9082,9085,9087,9089,9091,9094,9096,9098,9100,9102,9104,9106,9108,9111,9113,9116,9118,9120,9122,9124,9126],{"class":472,"line":5052},[470,9063,9064],{"class":5036},"  role",[470,9066,1149],{"class":4988},[470,9068,9004],{"class":5025},[470,9070,5028],{"class":4992},[470,9072,5006],{"class":499},[470,9074,9075],{"class":489},"role",[470,9077,5006],{"class":499},[470,9079,5109],{"class":4988},[470,9081,4989],{"class":4988},[470,9083,9084],{"class":5036}," enum",[470,9086,1149],{"class":4988},[470,9088,5077],{"class":4992},[470,9090,5006],{"class":499},[470,9092,9093],{"class":489},"owner",[470,9095,5006],{"class":499},[470,9097,5109],{"class":4988},[470,9099,500],{"class":499},[470,9101,5295],{"class":489},[470,9103,5006],{"class":499},[470,9105,5109],{"class":4988},[470,9107,500],{"class":499},[470,9109,9110],{"class":489},"member",[470,9112,5006],{"class":499},[470,9114,9115],{"class":4992},"] ",[470,9117,5191],{"class":4988},[470,9119,5194],{"class":4992},[470,9121,333],{"class":4988},[470,9123,9048],{"class":5025},[470,9125,6517],{"class":4992},[470,9127,5049],{"class":4988},[470,9129,9130,9133,9135,9138,9140,9142,9145,9147,9149,9151,9154,9156,9158,9160,9162],{"class":472,"line":5069},[470,9131,9132],{"class":5036},"  createdAt",[470,9134,1149],{"class":4988},[470,9136,9137],{"class":5025}," timestamp",[470,9139,5028],{"class":4992},[470,9141,5006],{"class":499},[470,9143,9144],{"class":489},"created_at",[470,9146,5006],{"class":499},[470,9148,5194],{"class":4992},[470,9150,333],{"class":4988},[470,9152,9153],{"class":5025},"defaultNow",[470,9155,6517],{"class":4992},[470,9157,333],{"class":4988},[470,9159,9048],{"class":5025},[470,9161,6517],{"class":4992},[470,9163,5049],{"class":4988},[470,9165,9166,9168,9170],{"class":472,"line":5092},[470,9167,5191],{"class":4988},[470,9169,5194],{"class":4992},[470,9171,5009],{"class":4988},[470,9173,9174],{"class":472,"line":5123},[470,9175,5014],{"emptyLinePlaceholder":22},[470,9177,9178,9180,9183,9187,9189,9192,9194,9196,9199],{"class":472,"line":5134},[470,9179,5019],{"class":4984},[470,9181,9182],{"class":5222}," type",[470,9184,9186],{"class":9185},"sKvfc"," User",[470,9188,5229],{"class":663},[470,9190,9191],{"class":663}," typeof",[470,9193,8976],{"class":4992},[470,9195,333],{"class":4988},[470,9197,9198],{"class":4992},"$inferSelect",[470,9200,5009],{"class":4988},[470,9202,9203,9205,9207,9210,9212,9214,9216,9218,9221],{"class":472,"line":5151},[470,9204,5019],{"class":4984},[470,9206,9182],{"class":5222},[470,9208,9209],{"class":9185}," NewUser",[470,9211,5229],{"class":663},[470,9213,9191],{"class":663},[470,9215,8976],{"class":4992},[470,9217,333],{"class":4988},[470,9219,9220],{"class":4992},"$inferInsert",[470,9222,5009],{"class":4988},[343,9224,9226],{"className":4975,"code":9225,"language":4977,"meta":349,"style":349},"\u002F\u002F 2. Procedimento de API Tipado (tRPC v11)\nexport const usersRouter = router({\n  getProfile: protectedProcedure\n    .input(z.object({ userId: z.string() }))\n    .query(async ({ ctx, input }) => {\n      const user = await ctx.db.query.users.findFirst({\n        where: eq(users.id, input.userId),\n      });\n      if (!user) throw new TRPCError({ code: \"NOT_FOUND\" });\n      return user; \u002F\u002F Tipo inferido automaticamente!\n    }),\n});\n",[325,9227,9228,9233,9251,9261,9302,9330,9364,9394,9403,9449,9462,9471],{"__ignoreMap":349},[470,9229,9230],{"class":472,"line":473},[470,9231,9232],{"class":5326},"\u002F\u002F 2. Procedimento de API Tipado (tRPC v11)\n",[470,9234,9235,9237,9239,9242,9244,9247,9249],{"class":472,"line":484},[470,9236,5019],{"class":4984},[470,9238,5223],{"class":5222},[470,9240,9241],{"class":5226}," usersRouter",[470,9243,5229],{"class":663},[470,9245,9246],{"class":5025}," router",[470,9248,5028],{"class":4992},[470,9250,5031],{"class":4988},[470,9252,9253,9256,9258],{"class":472,"line":509},[470,9254,9255],{"class":5036},"  getProfile",[470,9257,1149],{"class":4988},[470,9259,9260],{"class":4992}," protectedProcedure\n",[470,9262,9263,9266,9268,9271,9273,9276,9278,9281,9284,9286,9289,9291,9294,9297,9299],{"class":472,"line":528},[470,9264,9265],{"class":4988},"    .",[470,9267,1863],{"class":5025},[470,9269,9270],{"class":4992},"(z",[470,9272,333],{"class":4988},[470,9274,9275],{"class":5025},"object",[470,9277,5028],{"class":4992},[470,9279,9280],{"class":4988},"{",[470,9282,9283],{"class":5036}," userId",[470,9285,1149],{"class":4988},[470,9287,9288],{"class":4992}," z",[470,9290,333],{"class":4988},[470,9292,9293],{"class":5025},"string",[470,9295,9296],{"class":4992},"() ",[470,9298,5191],{"class":4988},[470,9300,9301],{"class":4992},"))\n",[470,9303,9304,9306,9308,9310,9312,9315,9318,9320,9323,9326,9328],{"class":472,"line":5052},[470,9305,9265],{"class":4988},[470,9307,6522],{"class":5025},[470,9309,5028],{"class":4992},[470,9311,5956],{"class":5955},[470,9313,9314],{"class":4988}," ({",[470,9316,9317],{"class":5961}," ctx",[470,9319,5109],{"class":4988},[470,9321,9322],{"class":5961}," input",[470,9324,9325],{"class":4988}," })",[470,9327,5967],{"class":5222},[470,9329,5131],{"class":4988},[470,9331,9332,9335,9337,9339,9341,9343,9345,9348,9350,9352,9354,9356,9358,9360,9362],{"class":472,"line":5069},[470,9333,9334],{"class":5222},"      const",[470,9336,6474],{"class":5226},[470,9338,5229],{"class":663},[470,9340,5982],{"class":4984},[470,9342,9317],{"class":4992},[470,9344,333],{"class":4988},[470,9346,9347],{"class":4992},"db",[470,9349,333],{"class":4988},[470,9351,6522],{"class":4992},[470,9353,333],{"class":4988},[470,9355,8988],{"class":4992},[470,9357,333],{"class":4988},[470,9359,6531],{"class":5025},[470,9361,5028],{"class":5036},[470,9363,5031],{"class":4988},[470,9365,9366,9369,9371,9373,9375,9377,9379,9381,9383,9385,9387,9390,9392],{"class":472,"line":5092},[470,9367,9368],{"class":5036},"        where",[470,9370,1149],{"class":4988},[470,9372,6545],{"class":5025},[470,9374,5028],{"class":5036},[470,9376,8988],{"class":4992},[470,9378,333],{"class":4988},[470,9380,6563],{"class":4992},[470,9382,5109],{"class":4988},[470,9384,9322],{"class":4992},[470,9386,333],{"class":4988},[470,9388,9389],{"class":4992},"userId",[470,9391,5194],{"class":5036},[470,9393,5049],{"class":4988},[470,9395,9396,9399,9401],{"class":472,"line":5123},[470,9397,9398],{"class":4988},"      }",[470,9400,5194],{"class":5036},[470,9402,5009],{"class":4988},[470,9404,9405,9408,9410,9413,9416,9419,9422,9424,9427,9429,9431,9434,9436,9438,9441,9443,9445,9447],{"class":472,"line":5134},[470,9406,9407],{"class":4984},"      if",[470,9409,401],{"class":5036},[470,9411,9412],{"class":663},"!",[470,9414,9415],{"class":4992},"user",[470,9417,9418],{"class":5036},") ",[470,9420,9421],{"class":4984},"throw",[470,9423,6215],{"class":663},[470,9425,9426],{"class":5025}," TRPCError",[470,9428,5028],{"class":5036},[470,9430,9280],{"class":4988},[470,9432,9433],{"class":5036}," code",[470,9435,1149],{"class":4988},[470,9437,500],{"class":499},[470,9439,9440],{"class":489},"NOT_FOUND",[470,9442,5006],{"class":499},[470,9444,4996],{"class":4988},[470,9446,5194],{"class":5036},[470,9448,5009],{"class":4988},[470,9450,9451,9454,9456,9459],{"class":472,"line":5151},[470,9452,9453],{"class":4984},"      return",[470,9455,6474],{"class":4992},[470,9457,9458],{"class":4988},";",[470,9460,9461],{"class":5326}," \u002F\u002F Tipo inferido automaticamente!\n",[470,9463,9464,9467,9469],{"class":472,"line":5168},[470,9465,9466],{"class":4988},"    }",[470,9468,5194],{"class":4992},[470,9470,5049],{"class":4988},[470,9472,9473,9475,9477],{"class":472,"line":5182},[470,9474,5191],{"class":4988},[470,9476,5194],{"class":4992},[470,9478,5009],{"class":4988},[343,9480,9484],{"className":9481,"code":9482,"language":9483,"meta":349,"style":349},"language-vue shiki shiki-themes material-theme-lighter github-light github-dark monokai","\u003C!-- 3. Consumo no Frontend Vue \u002F Nuxt com Zero Geração de Código -->\n\u003Cscript setup lang=\"ts\">\nconst { $trpc } = useNuxtApp();\nconst { data: profile, status } = await $trpc.users.getProfile.useQuery({\n  userId: \"usr_123\",\n});\n\n\u002F\u002F profile.value.email possui inferência total e autocompletion!\n\u003C\u002Fscript>\n","vue",[325,9485,9486,9491,9520,9541,9587,9603,9611,9615,9620],{"__ignoreMap":349},[470,9487,9488],{"class":472,"line":473},[470,9489,9490],{"class":5326},"\u003C!-- 3. Consumo no Frontend Vue \u002F Nuxt com Zero Geração de Código -->\n",[470,9492,9493,9496,9500,9504,9507,9510,9512,9515,9517],{"class":472,"line":484},[470,9494,9495],{"class":4988},"\u003C",[470,9497,9499],{"class":9498},"sHsBP","script",[470,9501,9503],{"class":9502},"s_lYk"," setup",[470,9505,9506],{"class":9502}," lang",[470,9508,9509],{"class":4988},"=",[470,9511,5006],{"class":499},[470,9513,9514],{"class":489},"ts",[470,9516,5006],{"class":499},[470,9518,9519],{"class":4988},">\n",[470,9521,9522,9525,9527,9530,9532,9534,9537,9539],{"class":472,"line":509},[470,9523,9524],{"class":5222},"const",[470,9526,4989],{"class":4988},[470,9528,9529],{"class":5226}," $trpc",[470,9531,4996],{"class":4988},[470,9533,5229],{"class":663},[470,9535,9536],{"class":5025}," useNuxtApp",[470,9538,6517],{"class":4992},[470,9540,5009],{"class":4988},[470,9542,9543,9545,9547,9551,9553,9556,9558,9561,9563,9565,9567,9569,9571,9573,9575,9578,9580,9583,9585],{"class":472,"line":528},[470,9544,9524],{"class":5222},[470,9546,4989],{"class":4988},[470,9548,9550],{"class":9549},"sIDdj"," data",[470,9552,1149],{"class":4988},[470,9554,9555],{"class":5226}," profile",[470,9557,5109],{"class":4988},[470,9559,9560],{"class":5226}," status",[470,9562,4996],{"class":4988},[470,9564,5229],{"class":663},[470,9566,5982],{"class":4984},[470,9568,9529],{"class":4992},[470,9570,333],{"class":4988},[470,9572,8988],{"class":4992},[470,9574,333],{"class":4988},[470,9576,9577],{"class":4992},"getProfile",[470,9579,333],{"class":4988},[470,9581,9582],{"class":5025},"useQuery",[470,9584,5028],{"class":4992},[470,9586,5031],{"class":4988},[470,9588,9589,9592,9594,9596,9599,9601],{"class":472,"line":5052},[470,9590,9591],{"class":5036},"  userId",[470,9593,1149],{"class":4988},[470,9595,500],{"class":499},[470,9597,9598],{"class":489},"usr_123",[470,9600,5006],{"class":499},[470,9602,5049],{"class":4988},[470,9604,9605,9607,9609],{"class":472,"line":5069},[470,9606,5191],{"class":4988},[470,9608,5194],{"class":4992},[470,9610,5009],{"class":4988},[470,9612,9613],{"class":472,"line":5092},[470,9614,5014],{"emptyLinePlaceholder":22},[470,9616,9617],{"class":472,"line":5123},[470,9618,9619],{"class":5326},"\u002F\u002F profile.value.email possui inferência total e autocompletion!\n",[470,9621,9622,9625,9627],{"class":472,"line":5134},[470,9623,9624],{"class":4988},"\u003C\u002F",[470,9626,9499],{"class":9498},[470,9628,9519],{"class":4988},[4903,9630],{},[338,9632,9634],{"id":9633},"benefícios-práticos-para-vibe-coding-e-ia","Benefícios Práticos para Vibe Coding e IA",[321,9636,9637],{},"Ao desenvolver com Cursor, GitHub Copilot ou Claude Code, as definições de tipo funcionam como o \"prompt de contexto\" mais poderoso possível. O agente de IA não precisa adivinhar o formato das respostas HTTP nem inventar nomes de tabelas: os tipos TypeScript orientam a geração do código correto na primeira tentativa.",[4903,9639],{},[338,9641,9643],{"id":9642},"conclusão","Conclusão",[321,9645,9646],{},"Adotar TypeScript desde o dia zero no seu SaaS economiza semanas de depuração e viabiliza um crescimento sustentável da base de código.",[321,9648,9649,9650,9652],{},"Se você quer pular a configuração manual de TypeScript, tRPC, Drizzle e Nuxt Layers e começar diretamente com uma arquitetura de nível enterprise pronta para produção, conheça o ",[455,9651,4900],{"href":5844}," e garanta acesso imediato ao código-fonte completo.",[1945,9654,9655],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .sHsBP, html code.shiki .sHsBP{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#F92672}html pre.shiki code .s_lYk, html code.shiki .s_lYk{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sIDdj, html code.shiki .sIDdj{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#F8F8F2}",{"title":349,"searchDepth":484,"depth":484,"links":9657},[9658,9659,9660,9661],{"id":8914,"depth":484,"text":8915},{"id":8953,"depth":484,"text":8954},{"id":9633,"depth":484,"text":9634},{"id":9642,"depth":484,"text":9643},"Aprenda as melhores práticas de TypeScript com tipagem estrita de ponta a ponta, tRPC e Drizzle ORM no Nuxt 4 para acelerar o desenvolvimento do seu SaaS.","\u002Fimages\u002Fblog\u002Ftypescript-end-to-end-typesafety.svg",{},"\u002Fimages\u002Fblog\u002Ftypescript-end-to-end-typesafety.png","\u002Fblog\u002Fgetting-started-with-typescript","5 min",{"title":8893,"description":9662},"blog\u002Fgetting-started-with-typescript",{"en":9671,"pt-br":9672,"es":9673},"typescript-nuxt-4-getting-started-guide","getting-started-with-typescript","primeros-pasos-typescript-nuxt-4","GfHt7MR66Aj6hc3T_RabFSDB2t3kh7ty563FDsAI4VM",{"id":9676,"title":9677,"author":1994,"authorType":316,"body":9678,"category":10116,"date":5401,"description":10117,"extension":1971,"image":10118,"lang":3428,"meta":10119,"navigation":22,"ogImage":10120,"path":10121,"readTime":5407,"seo":10122,"stem":10123,"tags":10124,"translations":10126,"updated":316,"__hash__":10127},"blog\u002Fblog\u002Fmodular-architecture-nuxt-layers-b2b.md","Modular Architecture with Nuxt Layers: Organizing B2B Features Without a Spaghetti Monolith",{"type":318,"value":9679,"toc":10108},[9680,9683,9694,9696,9700,9703,9728,9737,9743,9745,9751,9757,9932,9934,9940,9946,10061,10067,10069,10073,10096,10099,10106],[321,9681,9682],{},"As a SaaS product grows, the complexity of its code tends to explode. Checkout components get mixed with authentication rules, admin screens share directories with regular user screens, and a change to the database model breaks three different screens without warning.",[321,9684,2281,9685,9687,9688,9690,9691,9693],{},[330,9686,4892],{}," answer to this dilemma is called ",[330,9689,4896],{},". In ",[330,9692,4900],{},", we use Layers to build isolated modules that can be turned on or off with a single line of configuration.",[4903,9695],{},[338,9697,9699],{"id":9698},"what-is-a-nuxt-layer","What Is a Nuxt Layer?",[321,9701,9702],{},"A Layer in Nuxt is a complete, self-contained package that can hold:",[424,9704,9705,9713,9718,9723],{},[359,9706,9707,9708,9710,9711,1314],{},"Pages (",[325,9709,4919],{},") and layouts (",[325,9712,4923],{},[359,9714,9715,9716,1314],{},"Accessible Vue components (",[325,9717,4929],{},[359,9719,9720,9721,1314],{},"Server routes and tRPC procedures (",[325,9722,4935],{},[359,9724,9725,9726,1314],{},"Drizzle ORM schema slices (",[325,9727,4941],{},[321,9729,9730,9731,9734,9735,333],{},"This means the ",[330,9732,9733],{},"Billing"," feature, for example, lives entirely inside ",[325,9736,4951],{},[343,9738,9741],{"className":9739,"code":9740,"language":348},[346],"layers\u002F\n├── admin\u002F          # Platform admin panel, MFA and audit logs\n├── billing\u002F        # Stripe plans, checkout, webhooks and customer portal\n├── teams\u002F          # B2B multi-tenancy, member invitations and granular RBAC\n├── notifications\u002F  # WebSocket hub with Durable Objects and SQLite history\n└── kanban\u002F         # Optional module for interactive boards and cards\n",[325,9742,9740],{"__ignoreMap":349},[4903,9744],{},[338,9746,9748,9749],{"id":9747},"the-layer-contract-nuxfiremanifest","The Layer Contract: ",[325,9750,4966],{},[321,9752,9753,9754,9756],{},"For each layer to be truly modular without tight coupling, every layer exports a manifest using the shared ",[325,9755,4972],{}," helper:",[343,9758,9760],{"className":4975,"code":9759,"language":4977,"meta":349,"style":349},"import { defineLayerManifest } from \"@nuxfire\u002Fmanifest\";\n\nexport default defineLayerManifest({\n  name: \"billing\",\n  description: \"Stripe subscription management, webhooks and customer portal\",\n  dependencies: [\"teams\"],\n  routes: [\"\u002Fbilling\", \"\u002Fbilling\u002Fplans\"],\n  navigation: {\n    title: \"Subscription & Billing\",\n    icon: \"i-lucide-credit-card\",\n    path: \"\u002Fbilling\"\n  }\n});\n",[325,9761,9762,9782,9786,9798,9812,9827,9845,9871,9879,9894,9908,9920,9924],{"__ignoreMap":349},[470,9763,9764,9766,9768,9770,9772,9774,9776,9778,9780],{"class":472,"line":473},[470,9765,4985],{"class":4984},[470,9767,4989],{"class":4988},[470,9769,4993],{"class":4992},[470,9771,4996],{"class":4988},[470,9773,4999],{"class":4984},[470,9775,500],{"class":499},[470,9777,4966],{"class":489},[470,9779,5006],{"class":499},[470,9781,5009],{"class":4988},[470,9783,9784],{"class":472,"line":484},[470,9785,5014],{"emptyLinePlaceholder":22},[470,9787,9788,9790,9792,9794,9796],{"class":472,"line":509},[470,9789,5019],{"class":4984},[470,9791,5022],{"class":4984},[470,9793,4993],{"class":5025},[470,9795,5028],{"class":4992},[470,9797,5031],{"class":4988},[470,9799,9800,9802,9804,9806,9808,9810],{"class":472,"line":528},[470,9801,5037],{"class":5036},[470,9803,1149],{"class":4988},[470,9805,500],{"class":499},[470,9807,5044],{"class":489},[470,9809,5006],{"class":499},[470,9811,5049],{"class":4988},[470,9813,9814,9816,9818,9820,9823,9825],{"class":472,"line":5052},[470,9815,5055],{"class":5036},[470,9817,1149],{"class":4988},[470,9819,500],{"class":499},[470,9821,9822],{"class":489},"Stripe subscription management, webhooks and customer portal",[470,9824,5006],{"class":499},[470,9826,5049],{"class":4988},[470,9828,9829,9831,9833,9835,9837,9839,9841,9843],{"class":472,"line":5069},[470,9830,5072],{"class":5036},[470,9832,1149],{"class":4988},[470,9834,5077],{"class":4992},[470,9836,5006],{"class":499},[470,9838,5082],{"class":489},[470,9840,5006],{"class":499},[470,9842,5087],{"class":4992},[470,9844,5049],{"class":4988},[470,9846,9847,9849,9851,9853,9855,9857,9859,9861,9863,9865,9867,9869],{"class":472,"line":5092},[470,9848,5095],{"class":5036},[470,9850,1149],{"class":4988},[470,9852,5077],{"class":4992},[470,9854,5006],{"class":499},[470,9856,5104],{"class":489},[470,9858,5006],{"class":499},[470,9860,5109],{"class":4988},[470,9862,500],{"class":499},[470,9864,5114],{"class":489},[470,9866,5006],{"class":499},[470,9868,5087],{"class":4992},[470,9870,5049],{"class":4988},[470,9872,9873,9875,9877],{"class":472,"line":5123},[470,9874,5126],{"class":5036},[470,9876,1149],{"class":4988},[470,9878,5131],{"class":4988},[470,9880,9881,9883,9885,9887,9890,9892],{"class":472,"line":5134},[470,9882,5137],{"class":5036},[470,9884,1149],{"class":4988},[470,9886,500],{"class":499},[470,9888,9889],{"class":489},"Subscription & Billing",[470,9891,5006],{"class":499},[470,9893,5049],{"class":4988},[470,9895,9896,9898,9900,9902,9904,9906],{"class":472,"line":5151},[470,9897,5154],{"class":5036},[470,9899,1149],{"class":4988},[470,9901,500],{"class":499},[470,9903,5161],{"class":489},[470,9905,5006],{"class":499},[470,9907,5049],{"class":4988},[470,9909,9910,9912,9914,9916,9918],{"class":472,"line":5168},[470,9911,5171],{"class":5036},[470,9913,1149],{"class":4988},[470,9915,500],{"class":499},[470,9917,5104],{"class":489},[470,9919,506],{"class":499},[470,9921,9922],{"class":472,"line":5182},[470,9923,5185],{"class":4988},[470,9925,9926,9928,9930],{"class":472,"line":5188},[470,9927,5191],{"class":4988},[470,9929,5194],{"class":4992},[470,9931,5009],{"class":4988},[4903,9933],{},[338,9935,9937,9938],{"id":9936},"turning-modules-on-and-off-in-configts","Turning Modules On and Off in ",[325,9939,5205],{},[321,9941,9942,9943,9945],{},"Imagine you are building a lean product and you do not need the Kanban module or the complex notification system. In Nuxfire, you control the modules in the root ",[325,9944,5205],{}," file:",[343,9947,9949],{"className":4975,"code":9948,"language":4977,"meta":349,"style":349},"export const config = {\n  domain: \"yoursaas.com\",\n  layers: {\n    core: [\"teams\", \"billing\", \"notifications\", \"admin\"],\n    features: {\n      kanban: false, \u002F\u002F Turn it off easily without breaking the build\n    }\n  }\n};\n",[325,9950,9951,9963,9978,9986,10028,10036,10049,10053,10057],{"__ignoreMap":349},[470,9952,9953,9955,9957,9959,9961],{"class":472,"line":473},[470,9954,5019],{"class":4984},[470,9956,5223],{"class":5222},[470,9958,490],{"class":5226},[470,9960,5229],{"class":663},[470,9962,5131],{"class":4988},[470,9964,9965,9967,9969,9971,9974,9976],{"class":472,"line":484},[470,9966,5236],{"class":5036},[470,9968,1149],{"class":4988},[470,9970,500],{"class":499},[470,9972,9973],{"class":489},"yoursaas.com",[470,9975,5006],{"class":499},[470,9977,5049],{"class":4988},[470,9979,9980,9982,9984],{"class":472,"line":509},[470,9981,5252],{"class":5036},[470,9983,1149],{"class":4988},[470,9985,5131],{"class":4988},[470,9987,9988,9990,9992,9994,9996,9998,10000,10002,10004,10006,10008,10010,10012,10014,10016,10018,10020,10022,10024,10026],{"class":472,"line":528},[470,9989,5261],{"class":5036},[470,9991,1149],{"class":4988},[470,9993,5077],{"class":4992},[470,9995,5006],{"class":499},[470,9997,5082],{"class":489},[470,9999,5006],{"class":499},[470,10001,5109],{"class":4988},[470,10003,500],{"class":499},[470,10005,5044],{"class":489},[470,10007,5006],{"class":499},[470,10009,5109],{"class":4988},[470,10011,500],{"class":499},[470,10013,5286],{"class":489},[470,10015,5006],{"class":499},[470,10017,5109],{"class":4988},[470,10019,500],{"class":499},[470,10021,5295],{"class":489},[470,10023,5006],{"class":499},[470,10025,5087],{"class":4992},[470,10027,5049],{"class":4988},[470,10029,10030,10032,10034],{"class":472,"line":5052},[470,10031,5306],{"class":5036},[470,10033,1149],{"class":4988},[470,10035,5131],{"class":4988},[470,10037,10038,10040,10042,10044,10046],{"class":472,"line":5069},[470,10039,5315],{"class":5036},[470,10041,1149],{"class":4988},[470,10043,5321],{"class":5320},[470,10045,5109],{"class":4988},[470,10047,10048],{"class":5326}," \u002F\u002F Turn it off easily without breaking the build\n",[470,10050,10051],{"class":472,"line":5092},[470,10052,5332],{"class":4988},[470,10054,10055],{"class":472,"line":5123},[470,10056,5185],{"class":4988},[470,10058,10059],{"class":472,"line":5134},[470,10060,5341],{"class":4988},[321,10062,10063,10064,10066],{},"The build script inspects this file and assembles the final composition in ",[325,10065,5347],{}," 100% automatically.",[4903,10068],{},[338,10070,10072],{"id":10071},"practical-benefits-for-teams-and-developers","Practical Benefits for Teams and Developers",[356,10074,10075,10081,10087],{},[359,10076,10077,10080],{},[330,10078,10079],{},"Isolated Testability",": Each layer has its own unit and integration tests.",[359,10082,10083,10086],{},[330,10084,10085],{},"Immediate Reuse",": Create a new product by copying only the layers you need.",[359,10088,10089,10092,10093,10095],{},[330,10090,10091],{},"Ideal Context for AI",": When you ask an AI (Cursor, Claude Code) to edit the checkout, it only needs to read the files inside ",[325,10094,4951],{},", without loading thousands of irrelevant lines into memory.",[321,10097,10098],{},"Ready to transform your productivity and build on the right architecture from the start?",[321,10100,10101],{},[455,10102,10103],{"href":6318},[330,10104,10105],{},"Get immediate access to the Nuxfire source code and the VIP Group →",[1945,10107,5391],{},{"title":349,"searchDepth":484,"depth":484,"links":10109},[10110,10111,10113,10115],{"id":9698,"depth":484,"text":9699},{"id":9747,"depth":484,"text":10112},"The Layer Contract: @nuxfire\u002Fmanifest",{"id":9936,"depth":484,"text":10114},"Turning Modules On and Off in config.ts",{"id":10071,"depth":484,"text":10072},"Software Engineering","How to decouple Stripe billing, multi-tenant workspaces and the admin panel with Nuxt Layers and Bun monorepos to build clean, sustainable SaaS products.","\u002Fimages\u002Fblog\u002Fnuxt-layers-modular-b2b-en.svg",{},"\u002Fimages\u002Fblog\u002Fnuxt-layers-modular-b2b-en.png","\u002Fblog\u002Fmodular-architecture-nuxt-layers-b2b",{"title":9677,"description":10117},"blog\u002Fmodular-architecture-nuxt-layers-b2b",[4896,10125,5412,5413],"Modular Architecture",{"en":5415,"pt-br":5416,"es":5417},"NVy81nuVGAXk0v7tLDgsPb7eapJSi782FAMcC0ZZr2w",{"id":10129,"title":10130,"author":1994,"authorType":316,"body":10131,"category":11084,"date":5401,"description":11085,"extension":1971,"image":11086,"lang":3428,"meta":11087,"navigation":22,"ogImage":11088,"path":11089,"readTime":11090,"seo":11091,"stem":11092,"tags":11093,"translations":11097,"updated":316,"__hash__":11101},"blog\u002Fblog\u002Fmulti-tenancy-rbac-saas-security.md","Enterprise Multi-Tenancy and Granular RBAC on PostgreSQL with Drizzle ORM",{"type":318,"value":10132,"toc":11078},[10133,10142,10145,10160,10162,10166,10169,10186,10189,10642,10644,10648,10654,11017,11019,11023,11026,11048,11050,11054,11065,11068,11075],[321,10134,10135,10136,2074,10139,333],{},"The biggest dividing line between an amateur project and enterprise software ready for B2B sales is the security of ",[330,10137,10138],{},"multi-tenancy",[330,10140,10141],{},"RBAC (Role-Based Access Control)",[321,10143,10144],{},"If an enterprise customer discovers that a user from another company was able to view data from their workspace, your startup's reputation is destroyed on day one.",[321,10146,10147,10148,10151,10152,10154,10155,2074,10158,333],{},"In this article, we show how the ",[325,10149,10150],{},"layers\u002Fteams"," layer of ",[330,10153,4900],{}," implements failure-proof data isolation with ",[330,10156,10157],{},"PostgreSQL",[330,10159,6748],{},[4903,10161],{},[338,10163,10165],{"id":10164},"data-modeling-the-tenant-id-on-every-table","Data Modeling: The Tenant Id on Every Table",[321,10167,10168],{},"There are three classic forms of multi-tenancy:",[356,10170,10171,10174,10177],{},[359,10172,10173],{},"A separate database per customer (expensive and hard to keep migrations in sync).",[359,10175,10176],{},"A separate schema per customer (connection limits and complexity in the ORM).",[359,10178,10179,10185],{},[330,10180,10181,10182,5194],{},"Shared tables with a discriminator column (",[325,10183,10184],{},"team_id"," — The modern industry standard adopted by giants like Stripe, Slack and Linear.",[321,10187,10188],{},"In Nuxfire, every entity belongs to a team:",[343,10190,10192],{"className":4975,"code":10191,"language":4977,"meta":349,"style":349},"\u002F\u002F layers\u002Fteams\u002Fserver\u002Fdatabase\u002Fschema.ts\nexport const teams = pgTable(\"teams\", {\n  id: text(\"id\").primaryKey(),\n  name: text(\"name\").notNull(),\n  slug: text(\"slug\").notNull().unique(),\n  ownerId: text(\"owner_id\").notNull(),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull()\n});\n\nexport const teamMembers = pgTable(\"team_members\", {\n  id: text(\"id\").primaryKey(),\n  teamId: text(\"team_id\").notNull().references(() => teams.id, { onDelete: \"cascade\" }),\n  userId: text(\"user_id\").notNull(),\n  role: text(\"role\", { enum: [\"OWNER\", \"ADMIN\", \"MEMBER\"] }).notNull().default(\"MEMBER\"),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull()\n});\n",[325,10193,10194,10199,10224,10250,10277,10311,10339,10369,10377,10381,10407,10433,10497,10524,10604,10634],{"__ignoreMap":349},[470,10195,10196],{"class":472,"line":473},[470,10197,10198],{"class":5326},"\u002F\u002F layers\u002Fteams\u002Fserver\u002Fdatabase\u002Fschema.ts\n",[470,10200,10201,10203,10205,10208,10210,10212,10214,10216,10218,10220,10222],{"class":472,"line":484},[470,10202,5019],{"class":4984},[470,10204,5223],{"class":5222},[470,10206,10207],{"class":5226}," teams",[470,10209,5229],{"class":663},[470,10211,8981],{"class":5025},[470,10213,5028],{"class":4992},[470,10215,5006],{"class":499},[470,10217,5082],{"class":489},[470,10219,5006],{"class":499},[470,10221,5109],{"class":4988},[470,10223,5131],{"class":4988},[470,10225,10226,10228,10230,10232,10234,10236,10238,10240,10242,10244,10246,10248],{"class":472,"line":509},[470,10227,8999],{"class":5036},[470,10229,1149],{"class":4988},[470,10231,9004],{"class":5025},[470,10233,5028],{"class":4992},[470,10235,5006],{"class":499},[470,10237,6563],{"class":489},[470,10239,5006],{"class":499},[470,10241,5194],{"class":4992},[470,10243,333],{"class":4988},[470,10245,9019],{"class":5025},[470,10247,6517],{"class":4992},[470,10249,5049],{"class":4988},[470,10251,10252,10254,10256,10258,10260,10262,10265,10267,10269,10271,10273,10275],{"class":472,"line":528},[470,10253,5037],{"class":5036},[470,10255,1149],{"class":4988},[470,10257,9004],{"class":5025},[470,10259,5028],{"class":4992},[470,10261,5006],{"class":499},[470,10263,10264],{"class":489},"name",[470,10266,5006],{"class":499},[470,10268,5194],{"class":4992},[470,10270,333],{"class":4988},[470,10272,9048],{"class":5025},[470,10274,6517],{"class":4992},[470,10276,5049],{"class":4988},[470,10278,10279,10282,10284,10286,10288,10290,10293,10295,10297,10299,10301,10303,10305,10307,10309],{"class":472,"line":5052},[470,10280,10281],{"class":5036},"  slug",[470,10283,1149],{"class":4988},[470,10285,9004],{"class":5025},[470,10287,5028],{"class":4992},[470,10289,5006],{"class":499},[470,10291,10292],{"class":489},"slug",[470,10294,5006],{"class":499},[470,10296,5194],{"class":4992},[470,10298,333],{"class":4988},[470,10300,9048],{"class":5025},[470,10302,6517],{"class":4992},[470,10304,333],{"class":4988},[470,10306,9055],{"class":5025},[470,10308,6517],{"class":4992},[470,10310,5049],{"class":4988},[470,10312,10313,10316,10318,10320,10322,10324,10327,10329,10331,10333,10335,10337],{"class":472,"line":5069},[470,10314,10315],{"class":5036},"  ownerId",[470,10317,1149],{"class":4988},[470,10319,9004],{"class":5025},[470,10321,5028],{"class":4992},[470,10323,5006],{"class":499},[470,10325,10326],{"class":489},"owner_id",[470,10328,5006],{"class":499},[470,10330,5194],{"class":4992},[470,10332,333],{"class":4988},[470,10334,9048],{"class":5025},[470,10336,6517],{"class":4992},[470,10338,5049],{"class":4988},[470,10340,10341,10343,10345,10347,10349,10351,10353,10355,10357,10359,10361,10363,10365,10367],{"class":472,"line":5092},[470,10342,9132],{"class":5036},[470,10344,1149],{"class":4988},[470,10346,9137],{"class":5025},[470,10348,5028],{"class":4992},[470,10350,5006],{"class":499},[470,10352,9144],{"class":489},[470,10354,5006],{"class":499},[470,10356,5194],{"class":4992},[470,10358,333],{"class":4988},[470,10360,9153],{"class":5025},[470,10362,6517],{"class":4992},[470,10364,333],{"class":4988},[470,10366,9048],{"class":5025},[470,10368,6103],{"class":4992},[470,10370,10371,10373,10375],{"class":472,"line":5123},[470,10372,5191],{"class":4988},[470,10374,5194],{"class":4992},[470,10376,5009],{"class":4988},[470,10378,10379],{"class":472,"line":5134},[470,10380,5014],{"emptyLinePlaceholder":22},[470,10382,10383,10385,10387,10390,10392,10394,10396,10398,10401,10403,10405],{"class":472,"line":5151},[470,10384,5019],{"class":4984},[470,10386,5223],{"class":5222},[470,10388,10389],{"class":5226}," teamMembers",[470,10391,5229],{"class":663},[470,10393,8981],{"class":5025},[470,10395,5028],{"class":4992},[470,10397,5006],{"class":499},[470,10399,10400],{"class":489},"team_members",[470,10402,5006],{"class":499},[470,10404,5109],{"class":4988},[470,10406,5131],{"class":4988},[470,10408,10409,10411,10413,10415,10417,10419,10421,10423,10425,10427,10429,10431],{"class":472,"line":5168},[470,10410,8999],{"class":5036},[470,10412,1149],{"class":4988},[470,10414,9004],{"class":5025},[470,10416,5028],{"class":4992},[470,10418,5006],{"class":499},[470,10420,6563],{"class":489},[470,10422,5006],{"class":499},[470,10424,5194],{"class":4992},[470,10426,333],{"class":4988},[470,10428,9019],{"class":5025},[470,10430,6517],{"class":4992},[470,10432,5049],{"class":4988},[470,10434,10435,10438,10440,10442,10444,10446,10448,10450,10452,10454,10456,10458,10460,10463,10465,10467,10469,10471,10473,10475,10477,10479,10482,10484,10486,10489,10491,10493,10495],{"class":472,"line":5182},[470,10436,10437],{"class":5036},"  teamId",[470,10439,1149],{"class":4988},[470,10441,9004],{"class":5025},[470,10443,5028],{"class":4992},[470,10445,5006],{"class":499},[470,10447,10184],{"class":489},[470,10449,5006],{"class":499},[470,10451,5194],{"class":4992},[470,10453,333],{"class":4988},[470,10455,9048],{"class":5025},[470,10457,6517],{"class":4992},[470,10459,333],{"class":4988},[470,10461,10462],{"class":5025},"references",[470,10464,5028],{"class":4992},[470,10466,6517],{"class":4988},[470,10468,5967],{"class":5222},[470,10470,10207],{"class":4992},[470,10472,333],{"class":4988},[470,10474,6563],{"class":4992},[470,10476,5109],{"class":4988},[470,10478,4989],{"class":4988},[470,10480,10481],{"class":5036}," onDelete",[470,10483,1149],{"class":4988},[470,10485,500],{"class":499},[470,10487,10488],{"class":489},"cascade",[470,10490,5006],{"class":499},[470,10492,4996],{"class":4988},[470,10494,5194],{"class":4992},[470,10496,5049],{"class":4988},[470,10498,10499,10501,10503,10505,10507,10509,10512,10514,10516,10518,10520,10522],{"class":472,"line":5188},[470,10500,9591],{"class":5036},[470,10502,1149],{"class":4988},[470,10504,9004],{"class":5025},[470,10506,5028],{"class":4992},[470,10508,5006],{"class":499},[470,10510,10511],{"class":489},"user_id",[470,10513,5006],{"class":499},[470,10515,5194],{"class":4992},[470,10517,333],{"class":4988},[470,10519,9048],{"class":5025},[470,10521,6517],{"class":4992},[470,10523,5049],{"class":4988},[470,10525,10526,10528,10530,10532,10534,10536,10538,10540,10542,10544,10546,10548,10550,10552,10555,10557,10559,10561,10564,10566,10568,10570,10573,10575,10577,10579,10581,10583,10585,10587,10589,10592,10594,10596,10598,10600,10602],{"class":472,"line":6115},[470,10527,9064],{"class":5036},[470,10529,1149],{"class":4988},[470,10531,9004],{"class":5025},[470,10533,5028],{"class":4992},[470,10535,5006],{"class":499},[470,10537,9075],{"class":489},[470,10539,5006],{"class":499},[470,10541,5109],{"class":4988},[470,10543,4989],{"class":4988},[470,10545,9084],{"class":5036},[470,10547,1149],{"class":4988},[470,10549,5077],{"class":4992},[470,10551,5006],{"class":499},[470,10553,10554],{"class":489},"OWNER",[470,10556,5006],{"class":499},[470,10558,5109],{"class":4988},[470,10560,500],{"class":499},[470,10562,10563],{"class":489},"ADMIN",[470,10565,5006],{"class":499},[470,10567,5109],{"class":4988},[470,10569,500],{"class":499},[470,10571,10572],{"class":489},"MEMBER",[470,10574,5006],{"class":499},[470,10576,9115],{"class":4992},[470,10578,5191],{"class":4988},[470,10580,5194],{"class":4992},[470,10582,333],{"class":4988},[470,10584,9048],{"class":5025},[470,10586,6517],{"class":4992},[470,10588,333],{"class":4988},[470,10590,10591],{"class":5025},"default",[470,10593,5028],{"class":4992},[470,10595,5006],{"class":499},[470,10597,10572],{"class":489},[470,10599,5006],{"class":499},[470,10601,5194],{"class":4992},[470,10603,5049],{"class":4988},[470,10605,10606,10608,10610,10612,10614,10616,10618,10620,10622,10624,10626,10628,10630,10632],{"class":472,"line":6120},[470,10607,9132],{"class":5036},[470,10609,1149],{"class":4988},[470,10611,9137],{"class":5025},[470,10613,5028],{"class":4992},[470,10615,5006],{"class":499},[470,10617,9144],{"class":489},[470,10619,5006],{"class":499},[470,10621,5194],{"class":4992},[470,10623,333],{"class":4988},[470,10625,9153],{"class":5025},[470,10627,6517],{"class":4992},[470,10629,333],{"class":4988},[470,10631,9048],{"class":5025},[470,10633,6103],{"class":4992},[470,10635,10636,10638,10640],{"class":472,"line":6153},[470,10637,5191],{"class":4988},[470,10639,5194],{"class":4992},[470,10641,5009],{"class":4988},[4903,10643],{},[338,10645,10647],{"id":10646},"middleware-and-authenticated-trpc-procedures","Middleware and Authenticated tRPC Procedures",[321,10649,10650,10651,10653],{},"To make sure no developer forgets to filter by ",[325,10652,10184],{},", requests go through protected tRPC procedures:",[343,10655,10657],{"className":4975,"code":10656,"language":4977,"meta":349,"style":349},"export const teamProcedure = protectedProcedure\n  .input(z.object({ teamId: z.string() }))\n  .use(async ({ ctx, input, next }) => {\n    const membership = await ctx.db.query.teamMembers.findFirst({\n      where: and(\n        eq(teamMembers.teamId, input.teamId),\n        eq(teamMembers.userId, ctx.user.id)\n      )\n    });\n\n    if (!membership) {\n      throw new TRPCError({\n        code: \"FORBIDDEN\",\n        message: \"You do not have access to this workspace.\"\n      });\n    }\n\n    return next({\n      ctx: {\n        ...ctx,\n        teamId: input.teamId,\n        role: membership.role\n      }\n    });\n  });\n",[325,10658,10659,10672,10706,10736,10771,10784,10810,10836,10841,10849,10853,10869,10882,10898,10912,10920,10924,10929,10941,10951,10962,10978,10993,10999,11008],{"__ignoreMap":349},[470,10660,10661,10663,10665,10668,10670],{"class":472,"line":473},[470,10662,5019],{"class":4984},[470,10664,5223],{"class":5222},[470,10666,10667],{"class":5226}," teamProcedure",[470,10669,5229],{"class":663},[470,10671,9260],{"class":4992},[470,10673,10674,10677,10679,10681,10683,10685,10687,10689,10692,10694,10696,10698,10700,10702,10704],{"class":472,"line":484},[470,10675,10676],{"class":4988},"  .",[470,10678,1863],{"class":5025},[470,10680,9270],{"class":4992},[470,10682,333],{"class":4988},[470,10684,9275],{"class":5025},[470,10686,5028],{"class":4992},[470,10688,9280],{"class":4988},[470,10690,10691],{"class":5036}," teamId",[470,10693,1149],{"class":4988},[470,10695,9288],{"class":4992},[470,10697,333],{"class":4988},[470,10699,9293],{"class":5025},[470,10701,9296],{"class":4992},[470,10703,5191],{"class":4988},[470,10705,9301],{"class":4992},[470,10707,10708,10710,10713,10715,10717,10719,10721,10723,10725,10727,10730,10732,10734],{"class":472,"line":509},[470,10709,10676],{"class":4988},[470,10711,10712],{"class":5025},"use",[470,10714,5028],{"class":4992},[470,10716,5956],{"class":5955},[470,10718,9314],{"class":4988},[470,10720,9317],{"class":5961},[470,10722,5109],{"class":4988},[470,10724,9322],{"class":5961},[470,10726,5109],{"class":4988},[470,10728,10729],{"class":5961}," next",[470,10731,9325],{"class":4988},[470,10733,5967],{"class":5222},[470,10735,5131],{"class":4988},[470,10737,10738,10741,10744,10746,10748,10750,10752,10754,10756,10758,10760,10763,10765,10767,10769],{"class":472,"line":528},[470,10739,10740],{"class":5222},"    const",[470,10742,10743],{"class":5226}," membership",[470,10745,5229],{"class":663},[470,10747,5982],{"class":4984},[470,10749,9317],{"class":4992},[470,10751,333],{"class":4988},[470,10753,9347],{"class":4992},[470,10755,333],{"class":4988},[470,10757,6522],{"class":4992},[470,10759,333],{"class":4988},[470,10761,10762],{"class":4992},"teamMembers",[470,10764,333],{"class":4988},[470,10766,6531],{"class":5025},[470,10768,5028],{"class":5036},[470,10770,5031],{"class":4988},[470,10772,10773,10776,10778,10781],{"class":472,"line":5052},[470,10774,10775],{"class":5036},"      where",[470,10777,1149],{"class":4988},[470,10779,10780],{"class":5025}," and",[470,10782,10783],{"class":5036},"(\n",[470,10785,10786,10789,10791,10793,10795,10798,10800,10802,10804,10806,10808],{"class":472,"line":5069},[470,10787,10788],{"class":5025},"        eq",[470,10790,5028],{"class":5036},[470,10792,10762],{"class":4992},[470,10794,333],{"class":4988},[470,10796,10797],{"class":4992},"teamId",[470,10799,5109],{"class":4988},[470,10801,9322],{"class":4992},[470,10803,333],{"class":4988},[470,10805,10797],{"class":4992},[470,10807,5194],{"class":5036},[470,10809,5049],{"class":4988},[470,10811,10812,10814,10816,10818,10820,10822,10824,10826,10828,10830,10832,10834],{"class":472,"line":5092},[470,10813,10788],{"class":5025},[470,10815,5028],{"class":5036},[470,10817,10762],{"class":4992},[470,10819,333],{"class":4988},[470,10821,9389],{"class":4992},[470,10823,5109],{"class":4988},[470,10825,9317],{"class":4992},[470,10827,333],{"class":4988},[470,10829,9415],{"class":4992},[470,10831,333],{"class":4988},[470,10833,6563],{"class":4992},[470,10835,6271],{"class":5036},[470,10837,10838],{"class":472,"line":5123},[470,10839,10840],{"class":5036},"      )\n",[470,10842,10843,10845,10847],{"class":472,"line":5134},[470,10844,9466],{"class":4988},[470,10846,5194],{"class":5036},[470,10848,5009],{"class":4988},[470,10850,10851],{"class":472,"line":5151},[470,10852,5014],{"emptyLinePlaceholder":22},[470,10854,10855,10858,10860,10862,10865,10867],{"class":472,"line":5168},[470,10856,10857],{"class":4984},"    if",[470,10859,401],{"class":5036},[470,10861,9412],{"class":663},[470,10863,10864],{"class":4992},"membership",[470,10866,9418],{"class":5036},[470,10868,5031],{"class":4988},[470,10870,10871,10874,10876,10878,10880],{"class":472,"line":5182},[470,10872,10873],{"class":4984},"      throw",[470,10875,6215],{"class":663},[470,10877,9426],{"class":5025},[470,10879,5028],{"class":5036},[470,10881,5031],{"class":4988},[470,10883,10884,10887,10889,10891,10894,10896],{"class":472,"line":5188},[470,10885,10886],{"class":5036},"        code",[470,10888,1149],{"class":4988},[470,10890,500],{"class":499},[470,10892,10893],{"class":489},"FORBIDDEN",[470,10895,5006],{"class":499},[470,10897,5049],{"class":4988},[470,10899,10900,10903,10905,10907,10910],{"class":472,"line":6115},[470,10901,10902],{"class":5036},"        message",[470,10904,1149],{"class":4988},[470,10906,500],{"class":499},[470,10908,10909],{"class":489},"You do not have access to this workspace.",[470,10911,506],{"class":499},[470,10913,10914,10916,10918],{"class":472,"line":6120},[470,10915,9398],{"class":4988},[470,10917,5194],{"class":5036},[470,10919,5009],{"class":4988},[470,10921,10922],{"class":472,"line":6153},[470,10923,5332],{"class":4988},[470,10925,10927],{"class":472,"line":10926},17,[470,10928,5014],{"emptyLinePlaceholder":22},[470,10930,10932,10935,10937,10939],{"class":472,"line":10931},18,[470,10933,10934],{"class":4984},"    return",[470,10936,10729],{"class":5025},[470,10938,5028],{"class":5036},[470,10940,5031],{"class":4988},[470,10942,10944,10947,10949],{"class":472,"line":10943},19,[470,10945,10946],{"class":5036},"      ctx",[470,10948,1149],{"class":4988},[470,10950,5131],{"class":4988},[470,10952,10954,10957,10960],{"class":472,"line":10953},20,[470,10955,10956],{"class":663},"        ...",[470,10958,10959],{"class":4992},"ctx",[470,10961,5049],{"class":4988},[470,10963,10965,10968,10970,10972,10974,10976],{"class":472,"line":10964},21,[470,10966,10967],{"class":5036},"        teamId",[470,10969,1149],{"class":4988},[470,10971,9322],{"class":4992},[470,10973,333],{"class":4988},[470,10975,10797],{"class":4992},[470,10977,5049],{"class":4988},[470,10979,10981,10984,10986,10988,10990],{"class":472,"line":10980},22,[470,10982,10983],{"class":5036},"        role",[470,10985,1149],{"class":4988},[470,10987,10743],{"class":4992},[470,10989,333],{"class":4988},[470,10991,10992],{"class":4992},"role\n",[470,10994,10996],{"class":472,"line":10995},23,[470,10997,10998],{"class":4988},"      }\n",[470,11000,11002,11004,11006],{"class":472,"line":11001},24,[470,11003,9466],{"class":4988},[470,11005,5194],{"class":5036},[470,11007,5009],{"class":4988},[470,11009,11011,11013,11015],{"class":472,"line":11010},25,[470,11012,6108],{"class":4988},[470,11014,5194],{"class":4992},[470,11016,5009],{"class":4988},[4903,11018],{},[338,11020,11022],{"id":11021},"email-invitation-flow-with-an-encrypted-token","Email Invitation Flow with an Encrypted Token",[321,11024,11025],{},"Invitations for new members follow a secure double-confirmation flow:",[356,11027,11028,11035,11038,11045],{},[359,11029,11030,11031,2175,11033,1314],{},"The team administrator enters the collaborator's email and selects the role (",[325,11032,10563],{},[325,11034,10572],{},[359,11036,11037],{},"The system generates a cryptographically secure random token that expires in 7 days.",[359,11039,11040,11041,11044],{},"A polished email built with ",[330,11042,11043],{},"React Email"," is sent through an asynchronous queue.",[359,11046,11047],{},"When they click the link, the user is taken to the accept or onboarding screen, linking the account to the team instantly.",[4903,11049],{},[338,11051,11053],{"id":11052},"audit-logs-for-enterprise-compliance","Audit Logs for Enterprise Compliance",[321,11055,11056,11057,11060,11061,11064],{},"For demanding B2B customers, Nuxfire already includes a dedicated ",[330,11058,11059],{},"Audit Logs"," table (",[325,11062,11063],{},"\u002Fadmin\u002Faudit-log","). All critical changes — permission changes, member removals, plan changes and data downloads — are recorded immutably with a timestamp, IP and the author's identifier.",[321,11066,11067],{},"Build your SaaS on a foundation that meets the highest enterprise security standards from the very first commit.",[321,11069,11070],{},[455,11071,11072],{"href":6318},[330,11073,11074],{},"Get your Nuxfire license and secure immediate access to the code and the VIP Group →",[1945,11076,11077],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}",{"title":349,"searchDepth":484,"depth":484,"links":11079},[11080,11081,11082,11083],{"id":10164,"depth":484,"text":10165},{"id":10646,"depth":484,"text":10647},{"id":11021,"depth":484,"text":11022},{"id":11052,"depth":484,"text":11053},"Security & Databases","How to implement strict isolation between teams, granular permissions and secure token-based invitations with no security gaps in your B2B SaaS.","\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql-en.svg",{},"\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql-en.png","\u002Fblog\u002Fmulti-tenancy-rbac-saas-security","9 min",{"title":10130,"description":11085},"blog\u002Fmulti-tenancy-rbac-saas-security",[11094,11095,10157,6748,11096],"Multi-Tenancy","RBAC","Security",{"en":11098,"pt-br":11099,"es":11100},"multi-tenancy-rbac-saas-security","multi-tenancy-rbac-seguranca-saas","multi-tenancy-rbac-seguridad-saas","aA8BAEYe0nYoqTBeIYa_YEVt1er_cAxivk9vIwco58w",{"id":11103,"title":11104,"author":315,"authorType":316,"body":11105,"category":11993,"date":5401,"description":11994,"extension":1971,"image":11995,"lang":1973,"meta":11996,"navigation":22,"ogImage":11997,"path":11998,"readTime":11090,"seo":11999,"stem":12000,"tags":12001,"translations":12003,"updated":316,"__hash__":12004},"blog\u002Fblog\u002Fmulti-tenancy-rbac-seguranca-saas.md","Multi-Tenancy Corporativo e RBAC Granular no PostgreSQL com Drizzle ORM",{"type":318,"value":11106,"toc":11987},[11107,11115,11118,11131,11133,11137,11140,11156,11159,11593,11595,11599,11605,11931,11933,11937,11940,11961,11963,11967,11975,11978,11985],[321,11108,11109,11110,11112,11113,333],{},"O maior divisor de águas entre um projeto amador e um produto de software corporativo pronto para vendas B2B é a segurança do ",[330,11111,10138],{}," e do ",[330,11114,10141],{},[321,11116,11117],{},"Se um cliente corporativo descobrir que um usuário de outra empresa conseguiu visualizar dados do seu workspace, a reputação da sua startup é destruída no primeiro dia.",[321,11119,11120,11121,11123,11124,11126,11127,415,11129,333],{},"Neste artigo, mostramos como a camada ",[325,11122,10150],{}," do ",[330,11125,4900],{}," implementa isolamento de dados à prova de falhas com ",[330,11128,10157],{},[330,11130,6748],{},[4903,11132],{},[338,11134,11136],{"id":11135},"modelagem-de-dados-o-tenant-id-em-cada-tabela","Modelagem de Dados: O Tenant Id em Cada Tabela",[321,11138,11139],{},"Existem três formas clássicas de multi-tenancy:",[356,11141,11142,11145,11148],{},[359,11143,11144],{},"Banco de dados separado por cliente (caro e difícil de manter migrações).",[359,11146,11147],{},"Schema separado por cliente (limitações de conexões e complexidade no ORM).",[359,11149,11150,11155],{},[330,11151,11152,11153,5194],{},"Tabelas compartilhadas com coluna discriminadora (",[325,11154,10184],{}," — O padrão moderno da indústria adotado por gigantes como Stripe, Slack e Linear.",[321,11157,11158],{},"No Nuxfire, todas as entidades pertencem a uma equipe:",[343,11160,11161],{"className":4975,"code":10191,"language":4977,"meta":349,"style":349},[325,11162,11163,11167,11191,11217,11243,11275,11301,11331,11339,11343,11367,11393,11453,11479,11555,11585],{"__ignoreMap":349},[470,11164,11165],{"class":472,"line":473},[470,11166,10198],{"class":5326},[470,11168,11169,11171,11173,11175,11177,11179,11181,11183,11185,11187,11189],{"class":472,"line":484},[470,11170,5019],{"class":4984},[470,11172,5223],{"class":5222},[470,11174,10207],{"class":5226},[470,11176,5229],{"class":663},[470,11178,8981],{"class":5025},[470,11180,5028],{"class":4992},[470,11182,5006],{"class":499},[470,11184,5082],{"class":489},[470,11186,5006],{"class":499},[470,11188,5109],{"class":4988},[470,11190,5131],{"class":4988},[470,11192,11193,11195,11197,11199,11201,11203,11205,11207,11209,11211,11213,11215],{"class":472,"line":509},[470,11194,8999],{"class":5036},[470,11196,1149],{"class":4988},[470,11198,9004],{"class":5025},[470,11200,5028],{"class":4992},[470,11202,5006],{"class":499},[470,11204,6563],{"class":489},[470,11206,5006],{"class":499},[470,11208,5194],{"class":4992},[470,11210,333],{"class":4988},[470,11212,9019],{"class":5025},[470,11214,6517],{"class":4992},[470,11216,5049],{"class":4988},[470,11218,11219,11221,11223,11225,11227,11229,11231,11233,11235,11237,11239,11241],{"class":472,"line":528},[470,11220,5037],{"class":5036},[470,11222,1149],{"class":4988},[470,11224,9004],{"class":5025},[470,11226,5028],{"class":4992},[470,11228,5006],{"class":499},[470,11230,10264],{"class":489},[470,11232,5006],{"class":499},[470,11234,5194],{"class":4992},[470,11236,333],{"class":4988},[470,11238,9048],{"class":5025},[470,11240,6517],{"class":4992},[470,11242,5049],{"class":4988},[470,11244,11245,11247,11249,11251,11253,11255,11257,11259,11261,11263,11265,11267,11269,11271,11273],{"class":472,"line":5052},[470,11246,10281],{"class":5036},[470,11248,1149],{"class":4988},[470,11250,9004],{"class":5025},[470,11252,5028],{"class":4992},[470,11254,5006],{"class":499},[470,11256,10292],{"class":489},[470,11258,5006],{"class":499},[470,11260,5194],{"class":4992},[470,11262,333],{"class":4988},[470,11264,9048],{"class":5025},[470,11266,6517],{"class":4992},[470,11268,333],{"class":4988},[470,11270,9055],{"class":5025},[470,11272,6517],{"class":4992},[470,11274,5049],{"class":4988},[470,11276,11277,11279,11281,11283,11285,11287,11289,11291,11293,11295,11297,11299],{"class":472,"line":5069},[470,11278,10315],{"class":5036},[470,11280,1149],{"class":4988},[470,11282,9004],{"class":5025},[470,11284,5028],{"class":4992},[470,11286,5006],{"class":499},[470,11288,10326],{"class":489},[470,11290,5006],{"class":499},[470,11292,5194],{"class":4992},[470,11294,333],{"class":4988},[470,11296,9048],{"class":5025},[470,11298,6517],{"class":4992},[470,11300,5049],{"class":4988},[470,11302,11303,11305,11307,11309,11311,11313,11315,11317,11319,11321,11323,11325,11327,11329],{"class":472,"line":5092},[470,11304,9132],{"class":5036},[470,11306,1149],{"class":4988},[470,11308,9137],{"class":5025},[470,11310,5028],{"class":4992},[470,11312,5006],{"class":499},[470,11314,9144],{"class":489},[470,11316,5006],{"class":499},[470,11318,5194],{"class":4992},[470,11320,333],{"class":4988},[470,11322,9153],{"class":5025},[470,11324,6517],{"class":4992},[470,11326,333],{"class":4988},[470,11328,9048],{"class":5025},[470,11330,6103],{"class":4992},[470,11332,11333,11335,11337],{"class":472,"line":5123},[470,11334,5191],{"class":4988},[470,11336,5194],{"class":4992},[470,11338,5009],{"class":4988},[470,11340,11341],{"class":472,"line":5134},[470,11342,5014],{"emptyLinePlaceholder":22},[470,11344,11345,11347,11349,11351,11353,11355,11357,11359,11361,11363,11365],{"class":472,"line":5151},[470,11346,5019],{"class":4984},[470,11348,5223],{"class":5222},[470,11350,10389],{"class":5226},[470,11352,5229],{"class":663},[470,11354,8981],{"class":5025},[470,11356,5028],{"class":4992},[470,11358,5006],{"class":499},[470,11360,10400],{"class":489},[470,11362,5006],{"class":499},[470,11364,5109],{"class":4988},[470,11366,5131],{"class":4988},[470,11368,11369,11371,11373,11375,11377,11379,11381,11383,11385,11387,11389,11391],{"class":472,"line":5168},[470,11370,8999],{"class":5036},[470,11372,1149],{"class":4988},[470,11374,9004],{"class":5025},[470,11376,5028],{"class":4992},[470,11378,5006],{"class":499},[470,11380,6563],{"class":489},[470,11382,5006],{"class":499},[470,11384,5194],{"class":4992},[470,11386,333],{"class":4988},[470,11388,9019],{"class":5025},[470,11390,6517],{"class":4992},[470,11392,5049],{"class":4988},[470,11394,11395,11397,11399,11401,11403,11405,11407,11409,11411,11413,11415,11417,11419,11421,11423,11425,11427,11429,11431,11433,11435,11437,11439,11441,11443,11445,11447,11449,11451],{"class":472,"line":5182},[470,11396,10437],{"class":5036},[470,11398,1149],{"class":4988},[470,11400,9004],{"class":5025},[470,11402,5028],{"class":4992},[470,11404,5006],{"class":499},[470,11406,10184],{"class":489},[470,11408,5006],{"class":499},[470,11410,5194],{"class":4992},[470,11412,333],{"class":4988},[470,11414,9048],{"class":5025},[470,11416,6517],{"class":4992},[470,11418,333],{"class":4988},[470,11420,10462],{"class":5025},[470,11422,5028],{"class":4992},[470,11424,6517],{"class":4988},[470,11426,5967],{"class":5222},[470,11428,10207],{"class":4992},[470,11430,333],{"class":4988},[470,11432,6563],{"class":4992},[470,11434,5109],{"class":4988},[470,11436,4989],{"class":4988},[470,11438,10481],{"class":5036},[470,11440,1149],{"class":4988},[470,11442,500],{"class":499},[470,11444,10488],{"class":489},[470,11446,5006],{"class":499},[470,11448,4996],{"class":4988},[470,11450,5194],{"class":4992},[470,11452,5049],{"class":4988},[470,11454,11455,11457,11459,11461,11463,11465,11467,11469,11471,11473,11475,11477],{"class":472,"line":5188},[470,11456,9591],{"class":5036},[470,11458,1149],{"class":4988},[470,11460,9004],{"class":5025},[470,11462,5028],{"class":4992},[470,11464,5006],{"class":499},[470,11466,10511],{"class":489},[470,11468,5006],{"class":499},[470,11470,5194],{"class":4992},[470,11472,333],{"class":4988},[470,11474,9048],{"class":5025},[470,11476,6517],{"class":4992},[470,11478,5049],{"class":4988},[470,11480,11481,11483,11485,11487,11489,11491,11493,11495,11497,11499,11501,11503,11505,11507,11509,11511,11513,11515,11517,11519,11521,11523,11525,11527,11529,11531,11533,11535,11537,11539,11541,11543,11545,11547,11549,11551,11553],{"class":472,"line":6115},[470,11482,9064],{"class":5036},[470,11484,1149],{"class":4988},[470,11486,9004],{"class":5025},[470,11488,5028],{"class":4992},[470,11490,5006],{"class":499},[470,11492,9075],{"class":489},[470,11494,5006],{"class":499},[470,11496,5109],{"class":4988},[470,11498,4989],{"class":4988},[470,11500,9084],{"class":5036},[470,11502,1149],{"class":4988},[470,11504,5077],{"class":4992},[470,11506,5006],{"class":499},[470,11508,10554],{"class":489},[470,11510,5006],{"class":499},[470,11512,5109],{"class":4988},[470,11514,500],{"class":499},[470,11516,10563],{"class":489},[470,11518,5006],{"class":499},[470,11520,5109],{"class":4988},[470,11522,500],{"class":499},[470,11524,10572],{"class":489},[470,11526,5006],{"class":499},[470,11528,9115],{"class":4992},[470,11530,5191],{"class":4988},[470,11532,5194],{"class":4992},[470,11534,333],{"class":4988},[470,11536,9048],{"class":5025},[470,11538,6517],{"class":4992},[470,11540,333],{"class":4988},[470,11542,10591],{"class":5025},[470,11544,5028],{"class":4992},[470,11546,5006],{"class":499},[470,11548,10572],{"class":489},[470,11550,5006],{"class":499},[470,11552,5194],{"class":4992},[470,11554,5049],{"class":4988},[470,11556,11557,11559,11561,11563,11565,11567,11569,11571,11573,11575,11577,11579,11581,11583],{"class":472,"line":6120},[470,11558,9132],{"class":5036},[470,11560,1149],{"class":4988},[470,11562,9137],{"class":5025},[470,11564,5028],{"class":4992},[470,11566,5006],{"class":499},[470,11568,9144],{"class":489},[470,11570,5006],{"class":499},[470,11572,5194],{"class":4992},[470,11574,333],{"class":4988},[470,11576,9153],{"class":5025},[470,11578,6517],{"class":4992},[470,11580,333],{"class":4988},[470,11582,9048],{"class":5025},[470,11584,6103],{"class":4992},[470,11586,11587,11589,11591],{"class":472,"line":6153},[470,11588,5191],{"class":4988},[470,11590,5194],{"class":4992},[470,11592,5009],{"class":4988},[4903,11594],{},[338,11596,11598],{"id":11597},"middleware-e-procedures-trpc-autenticadas","Middleware e Procedures tRPC Autenticadas",[321,11600,11601,11602,11604],{},"Para garantir que nenhum desenvolvedor esqueça de filtrar pelo ",[325,11603,10184],{},", as requisições passam por procedimentos tRPC protegidos:",[343,11606,11608],{"className":4975,"code":11607,"language":4977,"meta":349,"style":349},"export const teamProcedure = protectedProcedure\n  .input(z.object({ teamId: z.string() }))\n  .use(async ({ ctx, input, next }) => {\n    const membership = await ctx.db.query.teamMembers.findFirst({\n      where: and(\n        eq(teamMembers.teamId, input.teamId),\n        eq(teamMembers.userId, ctx.user.id)\n      )\n    });\n\n    if (!membership) {\n      throw new TRPCError({\n        code: \"FORBIDDEN\",\n        message: \"Você não possui acesso a este workspace.\"\n      });\n    }\n\n    return next({\n      ctx: {\n        ...ctx,\n        teamId: input.teamId,\n        role: membership.role\n      }\n    });\n  });\n",[325,11609,11610,11622,11654,11682,11714,11724,11748,11774,11778,11786,11790,11804,11816,11830,11843,11851,11855,11859,11869,11877,11885,11899,11911,11915,11923],{"__ignoreMap":349},[470,11611,11612,11614,11616,11618,11620],{"class":472,"line":473},[470,11613,5019],{"class":4984},[470,11615,5223],{"class":5222},[470,11617,10667],{"class":5226},[470,11619,5229],{"class":663},[470,11621,9260],{"class":4992},[470,11623,11624,11626,11628,11630,11632,11634,11636,11638,11640,11642,11644,11646,11648,11650,11652],{"class":472,"line":484},[470,11625,10676],{"class":4988},[470,11627,1863],{"class":5025},[470,11629,9270],{"class":4992},[470,11631,333],{"class":4988},[470,11633,9275],{"class":5025},[470,11635,5028],{"class":4992},[470,11637,9280],{"class":4988},[470,11639,10691],{"class":5036},[470,11641,1149],{"class":4988},[470,11643,9288],{"class":4992},[470,11645,333],{"class":4988},[470,11647,9293],{"class":5025},[470,11649,9296],{"class":4992},[470,11651,5191],{"class":4988},[470,11653,9301],{"class":4992},[470,11655,11656,11658,11660,11662,11664,11666,11668,11670,11672,11674,11676,11678,11680],{"class":472,"line":509},[470,11657,10676],{"class":4988},[470,11659,10712],{"class":5025},[470,11661,5028],{"class":4992},[470,11663,5956],{"class":5955},[470,11665,9314],{"class":4988},[470,11667,9317],{"class":5961},[470,11669,5109],{"class":4988},[470,11671,9322],{"class":5961},[470,11673,5109],{"class":4988},[470,11675,10729],{"class":5961},[470,11677,9325],{"class":4988},[470,11679,5967],{"class":5222},[470,11681,5131],{"class":4988},[470,11683,11684,11686,11688,11690,11692,11694,11696,11698,11700,11702,11704,11706,11708,11710,11712],{"class":472,"line":528},[470,11685,10740],{"class":5222},[470,11687,10743],{"class":5226},[470,11689,5229],{"class":663},[470,11691,5982],{"class":4984},[470,11693,9317],{"class":4992},[470,11695,333],{"class":4988},[470,11697,9347],{"class":4992},[470,11699,333],{"class":4988},[470,11701,6522],{"class":4992},[470,11703,333],{"class":4988},[470,11705,10762],{"class":4992},[470,11707,333],{"class":4988},[470,11709,6531],{"class":5025},[470,11711,5028],{"class":5036},[470,11713,5031],{"class":4988},[470,11715,11716,11718,11720,11722],{"class":472,"line":5052},[470,11717,10775],{"class":5036},[470,11719,1149],{"class":4988},[470,11721,10780],{"class":5025},[470,11723,10783],{"class":5036},[470,11725,11726,11728,11730,11732,11734,11736,11738,11740,11742,11744,11746],{"class":472,"line":5069},[470,11727,10788],{"class":5025},[470,11729,5028],{"class":5036},[470,11731,10762],{"class":4992},[470,11733,333],{"class":4988},[470,11735,10797],{"class":4992},[470,11737,5109],{"class":4988},[470,11739,9322],{"class":4992},[470,11741,333],{"class":4988},[470,11743,10797],{"class":4992},[470,11745,5194],{"class":5036},[470,11747,5049],{"class":4988},[470,11749,11750,11752,11754,11756,11758,11760,11762,11764,11766,11768,11770,11772],{"class":472,"line":5092},[470,11751,10788],{"class":5025},[470,11753,5028],{"class":5036},[470,11755,10762],{"class":4992},[470,11757,333],{"class":4988},[470,11759,9389],{"class":4992},[470,11761,5109],{"class":4988},[470,11763,9317],{"class":4992},[470,11765,333],{"class":4988},[470,11767,9415],{"class":4992},[470,11769,333],{"class":4988},[470,11771,6563],{"class":4992},[470,11773,6271],{"class":5036},[470,11775,11776],{"class":472,"line":5123},[470,11777,10840],{"class":5036},[470,11779,11780,11782,11784],{"class":472,"line":5134},[470,11781,9466],{"class":4988},[470,11783,5194],{"class":5036},[470,11785,5009],{"class":4988},[470,11787,11788],{"class":472,"line":5151},[470,11789,5014],{"emptyLinePlaceholder":22},[470,11791,11792,11794,11796,11798,11800,11802],{"class":472,"line":5168},[470,11793,10857],{"class":4984},[470,11795,401],{"class":5036},[470,11797,9412],{"class":663},[470,11799,10864],{"class":4992},[470,11801,9418],{"class":5036},[470,11803,5031],{"class":4988},[470,11805,11806,11808,11810,11812,11814],{"class":472,"line":5182},[470,11807,10873],{"class":4984},[470,11809,6215],{"class":663},[470,11811,9426],{"class":5025},[470,11813,5028],{"class":5036},[470,11815,5031],{"class":4988},[470,11817,11818,11820,11822,11824,11826,11828],{"class":472,"line":5188},[470,11819,10886],{"class":5036},[470,11821,1149],{"class":4988},[470,11823,500],{"class":499},[470,11825,10893],{"class":489},[470,11827,5006],{"class":499},[470,11829,5049],{"class":4988},[470,11831,11832,11834,11836,11838,11841],{"class":472,"line":6115},[470,11833,10902],{"class":5036},[470,11835,1149],{"class":4988},[470,11837,500],{"class":499},[470,11839,11840],{"class":489},"Você não possui acesso a este workspace.",[470,11842,506],{"class":499},[470,11844,11845,11847,11849],{"class":472,"line":6120},[470,11846,9398],{"class":4988},[470,11848,5194],{"class":5036},[470,11850,5009],{"class":4988},[470,11852,11853],{"class":472,"line":6153},[470,11854,5332],{"class":4988},[470,11856,11857],{"class":472,"line":10926},[470,11858,5014],{"emptyLinePlaceholder":22},[470,11860,11861,11863,11865,11867],{"class":472,"line":10931},[470,11862,10934],{"class":4984},[470,11864,10729],{"class":5025},[470,11866,5028],{"class":5036},[470,11868,5031],{"class":4988},[470,11870,11871,11873,11875],{"class":472,"line":10943},[470,11872,10946],{"class":5036},[470,11874,1149],{"class":4988},[470,11876,5131],{"class":4988},[470,11878,11879,11881,11883],{"class":472,"line":10953},[470,11880,10956],{"class":663},[470,11882,10959],{"class":4992},[470,11884,5049],{"class":4988},[470,11886,11887,11889,11891,11893,11895,11897],{"class":472,"line":10964},[470,11888,10967],{"class":5036},[470,11890,1149],{"class":4988},[470,11892,9322],{"class":4992},[470,11894,333],{"class":4988},[470,11896,10797],{"class":4992},[470,11898,5049],{"class":4988},[470,11900,11901,11903,11905,11907,11909],{"class":472,"line":10980},[470,11902,10983],{"class":5036},[470,11904,1149],{"class":4988},[470,11906,10743],{"class":4992},[470,11908,333],{"class":4988},[470,11910,10992],{"class":4992},[470,11912,11913],{"class":472,"line":10995},[470,11914,10998],{"class":4988},[470,11916,11917,11919,11921],{"class":472,"line":11001},[470,11918,9466],{"class":4988},[470,11920,5194],{"class":5036},[470,11922,5009],{"class":4988},[470,11924,11925,11927,11929],{"class":472,"line":11010},[470,11926,6108],{"class":4988},[470,11928,5194],{"class":4992},[470,11930,5009],{"class":4988},[4903,11932],{},[338,11934,11936],{"id":11935},"fluxo-de-convites-por-e-mail-com-token-criptografado","Fluxo de Convites por E-mail com Token Criptografado",[321,11938,11939],{},"Convites para novos membros seguem um fluxo seguro de dupla confirmação:",[356,11941,11942,11949,11952,11958],{},[359,11943,11944,11945,550,11947,1314],{},"O administrador da equipe insere o e-mail do colaborador e seleciona o papel (",[325,11946,10563],{},[325,11948,10572],{},[359,11950,11951],{},"O sistema gera um token aleatório criptograficamente seguro com expiração de 7 dias.",[359,11953,11954,11955,11957],{},"Um e-mail elegante construído com ",[330,11956,11043],{}," é disparado via fila assíncrona.",[359,11959,11960],{},"Ao clicar no link, o usuário é direcionado para a tela de aceite ou onboarding, associando a conta à equipe instantaneamente.",[4903,11962],{},[338,11964,11966],{"id":11965},"logs-de-auditoria-para-conformidade-corporativa","Logs de Auditoria para Conformidade Corporativa",[321,11968,11969,11970,401,11972,11974],{},"Para clientes B2B exigentes, o Nuxfire já inclui uma tabela dedicada de ",[330,11971,11059],{},[325,11973,11063],{},"). Todas as alterações críticas — mudanças de permissões, remoção de membros, trocas de plano e downloads de dados — são registradas de forma imutável com timestamp, IP e identificador do autor.",[321,11976,11977],{},"Construa seu SaaS sobre uma fundação que atende aos mais altos padrões de segurança corporativa desde o primeiro commit.",[321,11979,11980],{},[455,11981,11982],{"href":5844},[330,11983,11984],{},"Adquira sua licença do Nuxfire e garanta acesso imediato ao código e ao Grupo VIP →",[1945,11986,11077],{},{"title":349,"searchDepth":484,"depth":484,"links":11988},[11989,11990,11991,11992],{"id":11135,"depth":484,"text":11136},{"id":11597,"depth":484,"text":11598},{"id":11935,"depth":484,"text":11936},{"id":11965,"depth":484,"text":11966},"Segurança & Banco de Dados","Como implementar isolamento estrito entre equipes, permissões granulares e convites seguros por token sem brechas de segurança no seu SaaS B2B.","\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql.svg",{},"\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql.png","\u002Fblog\u002Fmulti-tenancy-rbac-seguranca-saas",{"title":11104,"description":11994},"blog\u002Fmulti-tenancy-rbac-seguranca-saas",[11094,11095,10157,6748,12002],"Segurança",{"en":11098,"pt-br":11099,"es":11100},"aqhB0STzIYzhs34XIm6BPQoCM7JmcIGlfkNoyxDQOIQ",{"id":12006,"title":12007,"author":3439,"authorType":316,"body":12008,"category":12896,"date":5401,"description":12897,"extension":1971,"image":12898,"lang":4871,"meta":12899,"navigation":22,"ogImage":12900,"path":12901,"readTime":11090,"seo":12902,"stem":12903,"tags":12904,"translations":12906,"updated":316,"__hash__":12907},"blog\u002Fblog\u002Fmulti-tenancy-rbac-seguridad-saas.md","Multi-Tenancy Corporativo y RBAC Granular en PostgreSQL con Drizzle ORM",{"type":318,"value":12009,"toc":12890},[12010,12018,12021,12034,12036,12040,12043,12059,12062,12496,12498,12502,12508,12834,12836,12840,12843,12864,12866,12870,12878,12881,12888],[321,12011,12012,12013,12015,12016,333],{},"La mayor línea divisoria entre un proyecto amateur y un producto de software corporativo listo para las ventas B2B es la seguridad del ",[330,12014,10138],{}," y del ",[330,12017,10141],{},[321,12019,12020],{},"Si un cliente corporativo descubre que un usuario de otra empresa pudo ver datos de su workspace, la reputación de tu startup queda destruida el primer día.",[321,12022,12023,12024,12026,12027,12029,12030,3518,12032,333],{},"En este artículo mostramos cómo la capa ",[325,12025,10150],{}," de ",[330,12028,4900],{}," implementa un aislamiento de datos a prueba de fallos con ",[330,12031,10157],{},[330,12033,6748],{},[4903,12035],{},[338,12037,12039],{"id":12038},"modelado-de-datos-el-tenant-id-en-cada-tabla","Modelado de Datos: el Tenant Id en Cada Tabla",[321,12041,12042],{},"Existen tres formas clásicas de multi-tenancy:",[356,12044,12045,12048,12051],{},[359,12046,12047],{},"Una base de datos separada por cliente (cara y difícil de mantener en las migraciones).",[359,12049,12050],{},"Un schema separado por cliente (límites de conexiones y complejidad en el ORM).",[359,12052,12053,12058],{},[330,12054,12055,12056,5194],{},"Tablas compartidas con una columna discriminadora (",[325,12057,10184],{}," — El estándar moderno de la industria adoptado por gigantes como Stripe, Slack y Linear.",[321,12060,12061],{},"En Nuxfire, todas las entidades pertenecen a un equipo:",[343,12063,12064],{"className":4975,"code":10191,"language":4977,"meta":349,"style":349},[325,12065,12066,12070,12094,12120,12146,12178,12204,12234,12242,12246,12270,12296,12356,12382,12458,12488],{"__ignoreMap":349},[470,12067,12068],{"class":472,"line":473},[470,12069,10198],{"class":5326},[470,12071,12072,12074,12076,12078,12080,12082,12084,12086,12088,12090,12092],{"class":472,"line":484},[470,12073,5019],{"class":4984},[470,12075,5223],{"class":5222},[470,12077,10207],{"class":5226},[470,12079,5229],{"class":663},[470,12081,8981],{"class":5025},[470,12083,5028],{"class":4992},[470,12085,5006],{"class":499},[470,12087,5082],{"class":489},[470,12089,5006],{"class":499},[470,12091,5109],{"class":4988},[470,12093,5131],{"class":4988},[470,12095,12096,12098,12100,12102,12104,12106,12108,12110,12112,12114,12116,12118],{"class":472,"line":509},[470,12097,8999],{"class":5036},[470,12099,1149],{"class":4988},[470,12101,9004],{"class":5025},[470,12103,5028],{"class":4992},[470,12105,5006],{"class":499},[470,12107,6563],{"class":489},[470,12109,5006],{"class":499},[470,12111,5194],{"class":4992},[470,12113,333],{"class":4988},[470,12115,9019],{"class":5025},[470,12117,6517],{"class":4992},[470,12119,5049],{"class":4988},[470,12121,12122,12124,12126,12128,12130,12132,12134,12136,12138,12140,12142,12144],{"class":472,"line":528},[470,12123,5037],{"class":5036},[470,12125,1149],{"class":4988},[470,12127,9004],{"class":5025},[470,12129,5028],{"class":4992},[470,12131,5006],{"class":499},[470,12133,10264],{"class":489},[470,12135,5006],{"class":499},[470,12137,5194],{"class":4992},[470,12139,333],{"class":4988},[470,12141,9048],{"class":5025},[470,12143,6517],{"class":4992},[470,12145,5049],{"class":4988},[470,12147,12148,12150,12152,12154,12156,12158,12160,12162,12164,12166,12168,12170,12172,12174,12176],{"class":472,"line":5052},[470,12149,10281],{"class":5036},[470,12151,1149],{"class":4988},[470,12153,9004],{"class":5025},[470,12155,5028],{"class":4992},[470,12157,5006],{"class":499},[470,12159,10292],{"class":489},[470,12161,5006],{"class":499},[470,12163,5194],{"class":4992},[470,12165,333],{"class":4988},[470,12167,9048],{"class":5025},[470,12169,6517],{"class":4992},[470,12171,333],{"class":4988},[470,12173,9055],{"class":5025},[470,12175,6517],{"class":4992},[470,12177,5049],{"class":4988},[470,12179,12180,12182,12184,12186,12188,12190,12192,12194,12196,12198,12200,12202],{"class":472,"line":5069},[470,12181,10315],{"class":5036},[470,12183,1149],{"class":4988},[470,12185,9004],{"class":5025},[470,12187,5028],{"class":4992},[470,12189,5006],{"class":499},[470,12191,10326],{"class":489},[470,12193,5006],{"class":499},[470,12195,5194],{"class":4992},[470,12197,333],{"class":4988},[470,12199,9048],{"class":5025},[470,12201,6517],{"class":4992},[470,12203,5049],{"class":4988},[470,12205,12206,12208,12210,12212,12214,12216,12218,12220,12222,12224,12226,12228,12230,12232],{"class":472,"line":5092},[470,12207,9132],{"class":5036},[470,12209,1149],{"class":4988},[470,12211,9137],{"class":5025},[470,12213,5028],{"class":4992},[470,12215,5006],{"class":499},[470,12217,9144],{"class":489},[470,12219,5006],{"class":499},[470,12221,5194],{"class":4992},[470,12223,333],{"class":4988},[470,12225,9153],{"class":5025},[470,12227,6517],{"class":4992},[470,12229,333],{"class":4988},[470,12231,9048],{"class":5025},[470,12233,6103],{"class":4992},[470,12235,12236,12238,12240],{"class":472,"line":5123},[470,12237,5191],{"class":4988},[470,12239,5194],{"class":4992},[470,12241,5009],{"class":4988},[470,12243,12244],{"class":472,"line":5134},[470,12245,5014],{"emptyLinePlaceholder":22},[470,12247,12248,12250,12252,12254,12256,12258,12260,12262,12264,12266,12268],{"class":472,"line":5151},[470,12249,5019],{"class":4984},[470,12251,5223],{"class":5222},[470,12253,10389],{"class":5226},[470,12255,5229],{"class":663},[470,12257,8981],{"class":5025},[470,12259,5028],{"class":4992},[470,12261,5006],{"class":499},[470,12263,10400],{"class":489},[470,12265,5006],{"class":499},[470,12267,5109],{"class":4988},[470,12269,5131],{"class":4988},[470,12271,12272,12274,12276,12278,12280,12282,12284,12286,12288,12290,12292,12294],{"class":472,"line":5168},[470,12273,8999],{"class":5036},[470,12275,1149],{"class":4988},[470,12277,9004],{"class":5025},[470,12279,5028],{"class":4992},[470,12281,5006],{"class":499},[470,12283,6563],{"class":489},[470,12285,5006],{"class":499},[470,12287,5194],{"class":4992},[470,12289,333],{"class":4988},[470,12291,9019],{"class":5025},[470,12293,6517],{"class":4992},[470,12295,5049],{"class":4988},[470,12297,12298,12300,12302,12304,12306,12308,12310,12312,12314,12316,12318,12320,12322,12324,12326,12328,12330,12332,12334,12336,12338,12340,12342,12344,12346,12348,12350,12352,12354],{"class":472,"line":5182},[470,12299,10437],{"class":5036},[470,12301,1149],{"class":4988},[470,12303,9004],{"class":5025},[470,12305,5028],{"class":4992},[470,12307,5006],{"class":499},[470,12309,10184],{"class":489},[470,12311,5006],{"class":499},[470,12313,5194],{"class":4992},[470,12315,333],{"class":4988},[470,12317,9048],{"class":5025},[470,12319,6517],{"class":4992},[470,12321,333],{"class":4988},[470,12323,10462],{"class":5025},[470,12325,5028],{"class":4992},[470,12327,6517],{"class":4988},[470,12329,5967],{"class":5222},[470,12331,10207],{"class":4992},[470,12333,333],{"class":4988},[470,12335,6563],{"class":4992},[470,12337,5109],{"class":4988},[470,12339,4989],{"class":4988},[470,12341,10481],{"class":5036},[470,12343,1149],{"class":4988},[470,12345,500],{"class":499},[470,12347,10488],{"class":489},[470,12349,5006],{"class":499},[470,12351,4996],{"class":4988},[470,12353,5194],{"class":4992},[470,12355,5049],{"class":4988},[470,12357,12358,12360,12362,12364,12366,12368,12370,12372,12374,12376,12378,12380],{"class":472,"line":5188},[470,12359,9591],{"class":5036},[470,12361,1149],{"class":4988},[470,12363,9004],{"class":5025},[470,12365,5028],{"class":4992},[470,12367,5006],{"class":499},[470,12369,10511],{"class":489},[470,12371,5006],{"class":499},[470,12373,5194],{"class":4992},[470,12375,333],{"class":4988},[470,12377,9048],{"class":5025},[470,12379,6517],{"class":4992},[470,12381,5049],{"class":4988},[470,12383,12384,12386,12388,12390,12392,12394,12396,12398,12400,12402,12404,12406,12408,12410,12412,12414,12416,12418,12420,12422,12424,12426,12428,12430,12432,12434,12436,12438,12440,12442,12444,12446,12448,12450,12452,12454,12456],{"class":472,"line":6115},[470,12385,9064],{"class":5036},[470,12387,1149],{"class":4988},[470,12389,9004],{"class":5025},[470,12391,5028],{"class":4992},[470,12393,5006],{"class":499},[470,12395,9075],{"class":489},[470,12397,5006],{"class":499},[470,12399,5109],{"class":4988},[470,12401,4989],{"class":4988},[470,12403,9084],{"class":5036},[470,12405,1149],{"class":4988},[470,12407,5077],{"class":4992},[470,12409,5006],{"class":499},[470,12411,10554],{"class":489},[470,12413,5006],{"class":499},[470,12415,5109],{"class":4988},[470,12417,500],{"class":499},[470,12419,10563],{"class":489},[470,12421,5006],{"class":499},[470,12423,5109],{"class":4988},[470,12425,500],{"class":499},[470,12427,10572],{"class":489},[470,12429,5006],{"class":499},[470,12431,9115],{"class":4992},[470,12433,5191],{"class":4988},[470,12435,5194],{"class":4992},[470,12437,333],{"class":4988},[470,12439,9048],{"class":5025},[470,12441,6517],{"class":4992},[470,12443,333],{"class":4988},[470,12445,10591],{"class":5025},[470,12447,5028],{"class":4992},[470,12449,5006],{"class":499},[470,12451,10572],{"class":489},[470,12453,5006],{"class":499},[470,12455,5194],{"class":4992},[470,12457,5049],{"class":4988},[470,12459,12460,12462,12464,12466,12468,12470,12472,12474,12476,12478,12480,12482,12484,12486],{"class":472,"line":6120},[470,12461,9132],{"class":5036},[470,12463,1149],{"class":4988},[470,12465,9137],{"class":5025},[470,12467,5028],{"class":4992},[470,12469,5006],{"class":499},[470,12471,9144],{"class":489},[470,12473,5006],{"class":499},[470,12475,5194],{"class":4992},[470,12477,333],{"class":4988},[470,12479,9153],{"class":5025},[470,12481,6517],{"class":4992},[470,12483,333],{"class":4988},[470,12485,9048],{"class":5025},[470,12487,6103],{"class":4992},[470,12489,12490,12492,12494],{"class":472,"line":6153},[470,12491,5191],{"class":4988},[470,12493,5194],{"class":4992},[470,12495,5009],{"class":4988},[4903,12497],{},[338,12499,12501],{"id":12500},"middleware-y-procedures-trpc-autenticadas","Middleware y Procedures tRPC Autenticadas",[321,12503,12504,12505,12507],{},"Para garantizar que ningún desarrollador olvide filtrar por ",[325,12506,10184],{},", las peticiones pasan por procedimientos tRPC protegidos:",[343,12509,12511],{"className":4975,"code":12510,"language":4977,"meta":349,"style":349},"export const teamProcedure = protectedProcedure\n  .input(z.object({ teamId: z.string() }))\n  .use(async ({ ctx, input, next }) => {\n    const membership = await ctx.db.query.teamMembers.findFirst({\n      where: and(\n        eq(teamMembers.teamId, input.teamId),\n        eq(teamMembers.userId, ctx.user.id)\n      )\n    });\n\n    if (!membership) {\n      throw new TRPCError({\n        code: \"FORBIDDEN\",\n        message: \"No tienes acceso a este workspace.\"\n      });\n    }\n\n    return next({\n      ctx: {\n        ...ctx,\n        teamId: input.teamId,\n        role: membership.role\n      }\n    });\n  });\n",[325,12512,12513,12525,12557,12585,12617,12627,12651,12677,12681,12689,12693,12707,12719,12733,12746,12754,12758,12762,12772,12780,12788,12802,12814,12818,12826],{"__ignoreMap":349},[470,12514,12515,12517,12519,12521,12523],{"class":472,"line":473},[470,12516,5019],{"class":4984},[470,12518,5223],{"class":5222},[470,12520,10667],{"class":5226},[470,12522,5229],{"class":663},[470,12524,9260],{"class":4992},[470,12526,12527,12529,12531,12533,12535,12537,12539,12541,12543,12545,12547,12549,12551,12553,12555],{"class":472,"line":484},[470,12528,10676],{"class":4988},[470,12530,1863],{"class":5025},[470,12532,9270],{"class":4992},[470,12534,333],{"class":4988},[470,12536,9275],{"class":5025},[470,12538,5028],{"class":4992},[470,12540,9280],{"class":4988},[470,12542,10691],{"class":5036},[470,12544,1149],{"class":4988},[470,12546,9288],{"class":4992},[470,12548,333],{"class":4988},[470,12550,9293],{"class":5025},[470,12552,9296],{"class":4992},[470,12554,5191],{"class":4988},[470,12556,9301],{"class":4992},[470,12558,12559,12561,12563,12565,12567,12569,12571,12573,12575,12577,12579,12581,12583],{"class":472,"line":509},[470,12560,10676],{"class":4988},[470,12562,10712],{"class":5025},[470,12564,5028],{"class":4992},[470,12566,5956],{"class":5955},[470,12568,9314],{"class":4988},[470,12570,9317],{"class":5961},[470,12572,5109],{"class":4988},[470,12574,9322],{"class":5961},[470,12576,5109],{"class":4988},[470,12578,10729],{"class":5961},[470,12580,9325],{"class":4988},[470,12582,5967],{"class":5222},[470,12584,5131],{"class":4988},[470,12586,12587,12589,12591,12593,12595,12597,12599,12601,12603,12605,12607,12609,12611,12613,12615],{"class":472,"line":528},[470,12588,10740],{"class":5222},[470,12590,10743],{"class":5226},[470,12592,5229],{"class":663},[470,12594,5982],{"class":4984},[470,12596,9317],{"class":4992},[470,12598,333],{"class":4988},[470,12600,9347],{"class":4992},[470,12602,333],{"class":4988},[470,12604,6522],{"class":4992},[470,12606,333],{"class":4988},[470,12608,10762],{"class":4992},[470,12610,333],{"class":4988},[470,12612,6531],{"class":5025},[470,12614,5028],{"class":5036},[470,12616,5031],{"class":4988},[470,12618,12619,12621,12623,12625],{"class":472,"line":5052},[470,12620,10775],{"class":5036},[470,12622,1149],{"class":4988},[470,12624,10780],{"class":5025},[470,12626,10783],{"class":5036},[470,12628,12629,12631,12633,12635,12637,12639,12641,12643,12645,12647,12649],{"class":472,"line":5069},[470,12630,10788],{"class":5025},[470,12632,5028],{"class":5036},[470,12634,10762],{"class":4992},[470,12636,333],{"class":4988},[470,12638,10797],{"class":4992},[470,12640,5109],{"class":4988},[470,12642,9322],{"class":4992},[470,12644,333],{"class":4988},[470,12646,10797],{"class":4992},[470,12648,5194],{"class":5036},[470,12650,5049],{"class":4988},[470,12652,12653,12655,12657,12659,12661,12663,12665,12667,12669,12671,12673,12675],{"class":472,"line":5092},[470,12654,10788],{"class":5025},[470,12656,5028],{"class":5036},[470,12658,10762],{"class":4992},[470,12660,333],{"class":4988},[470,12662,9389],{"class":4992},[470,12664,5109],{"class":4988},[470,12666,9317],{"class":4992},[470,12668,333],{"class":4988},[470,12670,9415],{"class":4992},[470,12672,333],{"class":4988},[470,12674,6563],{"class":4992},[470,12676,6271],{"class":5036},[470,12678,12679],{"class":472,"line":5123},[470,12680,10840],{"class":5036},[470,12682,12683,12685,12687],{"class":472,"line":5134},[470,12684,9466],{"class":4988},[470,12686,5194],{"class":5036},[470,12688,5009],{"class":4988},[470,12690,12691],{"class":472,"line":5151},[470,12692,5014],{"emptyLinePlaceholder":22},[470,12694,12695,12697,12699,12701,12703,12705],{"class":472,"line":5168},[470,12696,10857],{"class":4984},[470,12698,401],{"class":5036},[470,12700,9412],{"class":663},[470,12702,10864],{"class":4992},[470,12704,9418],{"class":5036},[470,12706,5031],{"class":4988},[470,12708,12709,12711,12713,12715,12717],{"class":472,"line":5182},[470,12710,10873],{"class":4984},[470,12712,6215],{"class":663},[470,12714,9426],{"class":5025},[470,12716,5028],{"class":5036},[470,12718,5031],{"class":4988},[470,12720,12721,12723,12725,12727,12729,12731],{"class":472,"line":5188},[470,12722,10886],{"class":5036},[470,12724,1149],{"class":4988},[470,12726,500],{"class":499},[470,12728,10893],{"class":489},[470,12730,5006],{"class":499},[470,12732,5049],{"class":4988},[470,12734,12735,12737,12739,12741,12744],{"class":472,"line":6115},[470,12736,10902],{"class":5036},[470,12738,1149],{"class":4988},[470,12740,500],{"class":499},[470,12742,12743],{"class":489},"No tienes acceso a este workspace.",[470,12745,506],{"class":499},[470,12747,12748,12750,12752],{"class":472,"line":6120},[470,12749,9398],{"class":4988},[470,12751,5194],{"class":5036},[470,12753,5009],{"class":4988},[470,12755,12756],{"class":472,"line":6153},[470,12757,5332],{"class":4988},[470,12759,12760],{"class":472,"line":10926},[470,12761,5014],{"emptyLinePlaceholder":22},[470,12763,12764,12766,12768,12770],{"class":472,"line":10931},[470,12765,10934],{"class":4984},[470,12767,10729],{"class":5025},[470,12769,5028],{"class":5036},[470,12771,5031],{"class":4988},[470,12773,12774,12776,12778],{"class":472,"line":10943},[470,12775,10946],{"class":5036},[470,12777,1149],{"class":4988},[470,12779,5131],{"class":4988},[470,12781,12782,12784,12786],{"class":472,"line":10953},[470,12783,10956],{"class":663},[470,12785,10959],{"class":4992},[470,12787,5049],{"class":4988},[470,12789,12790,12792,12794,12796,12798,12800],{"class":472,"line":10964},[470,12791,10967],{"class":5036},[470,12793,1149],{"class":4988},[470,12795,9322],{"class":4992},[470,12797,333],{"class":4988},[470,12799,10797],{"class":4992},[470,12801,5049],{"class":4988},[470,12803,12804,12806,12808,12810,12812],{"class":472,"line":10980},[470,12805,10983],{"class":5036},[470,12807,1149],{"class":4988},[470,12809,10743],{"class":4992},[470,12811,333],{"class":4988},[470,12813,10992],{"class":4992},[470,12815,12816],{"class":472,"line":10995},[470,12817,10998],{"class":4988},[470,12819,12820,12822,12824],{"class":472,"line":11001},[470,12821,9466],{"class":4988},[470,12823,5194],{"class":5036},[470,12825,5009],{"class":4988},[470,12827,12828,12830,12832],{"class":472,"line":11010},[470,12829,6108],{"class":4988},[470,12831,5194],{"class":4992},[470,12833,5009],{"class":4988},[4903,12835],{},[338,12837,12839],{"id":12838},"flujo-de-invitaciones-por-correo-con-token-cifrado","Flujo de Invitaciones por Correo con Token Cifrado",[321,12841,12842],{},"Las invitaciones a nuevos miembros siguen un flujo seguro de doble confirmación:",[356,12844,12845,12852,12855,12861],{},[359,12846,12847,12848,3620,12850,1314],{},"El administrador del equipo ingresa el correo del colaborador y selecciona el rol (",[325,12849,10563],{},[325,12851,10572],{},[359,12853,12854],{},"El sistema genera un token aleatorio criptográficamente seguro que expira en 7 días.",[359,12856,12857,12858,12860],{},"Un correo elegante construido con ",[330,12859,11043],{}," se envía mediante una cola asíncrona.",[359,12862,12863],{},"Al hacer clic en el enlace, el usuario es dirigido a la pantalla de aceptación o de onboarding, y la cuenta queda asociada al equipo al instante.",[4903,12865],{},[338,12867,12869],{"id":12868},"registros-de-auditoría-para-el-cumplimiento-corporativo","Registros de Auditoría para el Cumplimiento Corporativo",[321,12871,12872,12873,401,12875,12877],{},"Para los clientes B2B exigentes, Nuxfire ya incluye una tabla dedicada de ",[330,12874,11059],{},[325,12876,11063],{},"). Todos los cambios críticos — cambios de permisos, eliminación de miembros, cambios de plan y descargas de datos — se registran de forma inmutable con marca de tiempo, IP e identificador del autor.",[321,12879,12880],{},"Construye tu SaaS sobre una base que cumple los más altos estándares de seguridad corporativa desde el primer commit.",[321,12882,12883],{},[455,12884,12885],{"href":5385},[330,12886,12887],{},"Adquiere tu licencia de Nuxfire y asegura el acceso inmediato al código y al Grupo VIP →",[1945,12889,11077],{},{"title":349,"searchDepth":484,"depth":484,"links":12891},[12892,12893,12894,12895],{"id":12038,"depth":484,"text":12039},{"id":12500,"depth":484,"text":12501},{"id":12838,"depth":484,"text":12839},{"id":12868,"depth":484,"text":12869},"Seguridad y Bases de Datos","Cómo implementar un aislamiento estricto entre equipos, permisos granulares e invitaciones seguras por token sin brechas de seguridad en tu SaaS B2B.","\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql-es.svg",{},"\u002Fimages\u002Fblog\u002Fmulti-tenancy-rbac-postgresql-es.png","\u002Fblog\u002Fmulti-tenancy-rbac-seguridad-saas",{"title":12007,"description":12897},"blog\u002Fmulti-tenancy-rbac-seguridad-saas",[11094,11095,10157,6748,12905],"Seguridad",{"en":11098,"pt-br":11099,"es":11100},"GNHVMWzHphyshkD9jBmOeOk3fIUKw8U2yJD1Ywxdtm0",{"id":12909,"title":12910,"author":8894,"authorType":8895,"body":12911,"category":8902,"date":5401,"description":13590,"extension":1971,"image":13591,"lang":4871,"meta":13592,"navigation":22,"ogImage":13593,"path":13594,"readTime":9667,"seo":13595,"stem":13596,"tags":316,"translations":13597,"updated":316,"__hash__":13598},"blog\u002Fblog\u002Fprimeros-pasos-typescript-nuxt-4.md","Primeros Pasos con TypeScript en Nuxt 4: Guía Completa para SaaS",{"type":318,"value":12912,"toc":13584},[12913,12918,12924,12926,12930,12933,12961,12963,12967,12970,13208,13431,13558,13560,13564,13567,13569,13573,13576,13582],[321,12914,12915,12917],{},[330,12916,8902],{}," se ha vuelto un requisito indispensable en el desarrollo de software moderno, especialmente en la construcción de aplicaciones SaaS críticas donde los errores en tiempo de ejecución cuestan clientes y reputación.",[321,12919,12920,12921,12923],{},"En el ecosistema de ",[330,12922,4900],{},", TypeScript no es solo un linter ni una comprobación superficial de tipos: es el cimiento fundamental que conecta la base de datos PostgreSQL con el cliente Vue 3, pasando por la capa de APIs con tRPC y por la infraestructura con SST v4.",[4903,12925],{},[338,12927,12929],{"id":12928},"por-qué-el-tipado-estricto-es-crítico-en-saas","¿Por qué el Tipado Estricto es Crítico en SaaS?",[321,12931,12932],{},"Construir software escalable exige previsibilidad. Cuando varios desarrolladores (o asistentes de IA como Cursor y Claude Code) colaboran en el mismo código, el tipado estricto garantiza:",[356,12934,12935,12941,12949,12955],{},[359,12936,12937,12940],{},[330,12938,12939],{},"Refactorizaciones sin Miedo",": Renombra campos en la base de datos o cambia firmas de funciones y el compilador señalará exactamente qué pantallas necesitan ajustes.",[359,12942,12943,8932,12946,12948],{},[330,12944,12945],{},"Eliminación de Clases Enteras de Bugs",[325,12947,8935],{}," y los accesos a propiedades inexistentes se interceptan antes del commit.",[359,12950,12951,12954],{},[330,12952,12953],{},"Autocompletado Inteligente en el IDE",": Productividad multiplicada con sugerencias instantáneas de parámetros, retornos y tipos utilitarios.",[359,12956,12957,12960],{},[330,12958,12959],{},"Alineación Perfecta con las Herramientas de IA",": Los LLM producen código sustancialmente mejor cuando los guían definiciones estrictas de interfaces.",[4903,12962],{},[338,12964,12966],{"id":12965},"el-flujo-de-tipado-de-extremo-a-extremo-en-nuxfire","El Flujo de Tipado de Extremo a Extremo en Nuxfire",[321,12968,12969],{},"La arquitectura de Nuxfire implementa una cadena completa de inferencia de tipos en 3 capas:",[343,12971,12973],{"className":4975,"code":12972,"language":4977,"meta":349,"style":349},"\u002F\u002F 1. Schema de la Base de Datos (Drizzle ORM)\nexport const users = pgTable(\"users\", {\n  id: text(\"id\").primaryKey(),\n  email: text(\"email\").notNull().unique(),\n  role: text(\"role\", { enum: [\"owner\", \"admin\", \"member\"] }).notNull(),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n});\n\nexport type User = typeof users.$inferSelect;\nexport type NewUser = typeof users.$inferInsert;\n",[325,12974,12975,12980,13004,13030,13062,13124,13156,13164,13168,13188],{"__ignoreMap":349},[470,12976,12977],{"class":472,"line":473},[470,12978,12979],{"class":5326},"\u002F\u002F 1. Schema de la Base de Datos (Drizzle ORM)\n",[470,12981,12982,12984,12986,12988,12990,12992,12994,12996,12998,13000,13002],{"class":472,"line":484},[470,12983,5019],{"class":4984},[470,12985,5223],{"class":5222},[470,12987,8976],{"class":5226},[470,12989,5229],{"class":663},[470,12991,8981],{"class":5025},[470,12993,5028],{"class":4992},[470,12995,5006],{"class":499},[470,12997,8988],{"class":489},[470,12999,5006],{"class":499},[470,13001,5109],{"class":4988},[470,13003,5131],{"class":4988},[470,13005,13006,13008,13010,13012,13014,13016,13018,13020,13022,13024,13026,13028],{"class":472,"line":509},[470,13007,8999],{"class":5036},[470,13009,1149],{"class":4988},[470,13011,9004],{"class":5025},[470,13013,5028],{"class":4992},[470,13015,5006],{"class":499},[470,13017,6563],{"class":489},[470,13019,5006],{"class":499},[470,13021,5194],{"class":4992},[470,13023,333],{"class":4988},[470,13025,9019],{"class":5025},[470,13027,6517],{"class":4992},[470,13029,5049],{"class":4988},[470,13031,13032,13034,13036,13038,13040,13042,13044,13046,13048,13050,13052,13054,13056,13058,13060],{"class":472,"line":528},[470,13033,9028],{"class":5036},[470,13035,1149],{"class":4988},[470,13037,9004],{"class":5025},[470,13039,5028],{"class":4992},[470,13041,5006],{"class":499},[470,13043,9039],{"class":489},[470,13045,5006],{"class":499},[470,13047,5194],{"class":4992},[470,13049,333],{"class":4988},[470,13051,9048],{"class":5025},[470,13053,6517],{"class":4992},[470,13055,333],{"class":4988},[470,13057,9055],{"class":5025},[470,13059,6517],{"class":4992},[470,13061,5049],{"class":4988},[470,13063,13064,13066,13068,13070,13072,13074,13076,13078,13080,13082,13084,13086,13088,13090,13092,13094,13096,13098,13100,13102,13104,13106,13108,13110,13112,13114,13116,13118,13120,13122],{"class":472,"line":5052},[470,13065,9064],{"class":5036},[470,13067,1149],{"class":4988},[470,13069,9004],{"class":5025},[470,13071,5028],{"class":4992},[470,13073,5006],{"class":499},[470,13075,9075],{"class":489},[470,13077,5006],{"class":499},[470,13079,5109],{"class":4988},[470,13081,4989],{"class":4988},[470,13083,9084],{"class":5036},[470,13085,1149],{"class":4988},[470,13087,5077],{"class":4992},[470,13089,5006],{"class":499},[470,13091,9093],{"class":489},[470,13093,5006],{"class":499},[470,13095,5109],{"class":4988},[470,13097,500],{"class":499},[470,13099,5295],{"class":489},[470,13101,5006],{"class":499},[470,13103,5109],{"class":4988},[470,13105,500],{"class":499},[470,13107,9110],{"class":489},[470,13109,5006],{"class":499},[470,13111,9115],{"class":4992},[470,13113,5191],{"class":4988},[470,13115,5194],{"class":4992},[470,13117,333],{"class":4988},[470,13119,9048],{"class":5025},[470,13121,6517],{"class":4992},[470,13123,5049],{"class":4988},[470,13125,13126,13128,13130,13132,13134,13136,13138,13140,13142,13144,13146,13148,13150,13152,13154],{"class":472,"line":5069},[470,13127,9132],{"class":5036},[470,13129,1149],{"class":4988},[470,13131,9137],{"class":5025},[470,13133,5028],{"class":4992},[470,13135,5006],{"class":499},[470,13137,9144],{"class":489},[470,13139,5006],{"class":499},[470,13141,5194],{"class":4992},[470,13143,333],{"class":4988},[470,13145,9153],{"class":5025},[470,13147,6517],{"class":4992},[470,13149,333],{"class":4988},[470,13151,9048],{"class":5025},[470,13153,6517],{"class":4992},[470,13155,5049],{"class":4988},[470,13157,13158,13160,13162],{"class":472,"line":5092},[470,13159,5191],{"class":4988},[470,13161,5194],{"class":4992},[470,13163,5009],{"class":4988},[470,13165,13166],{"class":472,"line":5123},[470,13167,5014],{"emptyLinePlaceholder":22},[470,13169,13170,13172,13174,13176,13178,13180,13182,13184,13186],{"class":472,"line":5134},[470,13171,5019],{"class":4984},[470,13173,9182],{"class":5222},[470,13175,9186],{"class":9185},[470,13177,5229],{"class":663},[470,13179,9191],{"class":663},[470,13181,8976],{"class":4992},[470,13183,333],{"class":4988},[470,13185,9198],{"class":4992},[470,13187,5009],{"class":4988},[470,13189,13190,13192,13194,13196,13198,13200,13202,13204,13206],{"class":472,"line":5151},[470,13191,5019],{"class":4984},[470,13193,9182],{"class":5222},[470,13195,9209],{"class":9185},[470,13197,5229],{"class":663},[470,13199,9191],{"class":663},[470,13201,8976],{"class":4992},[470,13203,333],{"class":4988},[470,13205,9220],{"class":4992},[470,13207,5009],{"class":4988},[343,13209,13211],{"className":4975,"code":13210,"language":4977,"meta":349,"style":349},"\u002F\u002F 2. Procedimiento de API Tipado (tRPC v11)\nexport const usersRouter = router({\n  getProfile: protectedProcedure\n    .input(z.object({ userId: z.string() }))\n    .query(async ({ ctx, input }) => {\n      const user = await ctx.db.query.users.findFirst({\n        where: eq(users.id, input.userId),\n      });\n      if (!user) throw new TRPCError({ code: \"NOT_FOUND\" });\n      return user; \u002F\u002F ¡Tipo inferido automáticamente!\n    }),\n});\n",[325,13212,13213,13218,13234,13242,13274,13298,13330,13358,13366,13404,13415,13423],{"__ignoreMap":349},[470,13214,13215],{"class":472,"line":473},[470,13216,13217],{"class":5326},"\u002F\u002F 2. Procedimiento de API Tipado (tRPC v11)\n",[470,13219,13220,13222,13224,13226,13228,13230,13232],{"class":472,"line":484},[470,13221,5019],{"class":4984},[470,13223,5223],{"class":5222},[470,13225,9241],{"class":5226},[470,13227,5229],{"class":663},[470,13229,9246],{"class":5025},[470,13231,5028],{"class":4992},[470,13233,5031],{"class":4988},[470,13235,13236,13238,13240],{"class":472,"line":509},[470,13237,9255],{"class":5036},[470,13239,1149],{"class":4988},[470,13241,9260],{"class":4992},[470,13243,13244,13246,13248,13250,13252,13254,13256,13258,13260,13262,13264,13266,13268,13270,13272],{"class":472,"line":528},[470,13245,9265],{"class":4988},[470,13247,1863],{"class":5025},[470,13249,9270],{"class":4992},[470,13251,333],{"class":4988},[470,13253,9275],{"class":5025},[470,13255,5028],{"class":4992},[470,13257,9280],{"class":4988},[470,13259,9283],{"class":5036},[470,13261,1149],{"class":4988},[470,13263,9288],{"class":4992},[470,13265,333],{"class":4988},[470,13267,9293],{"class":5025},[470,13269,9296],{"class":4992},[470,13271,5191],{"class":4988},[470,13273,9301],{"class":4992},[470,13275,13276,13278,13280,13282,13284,13286,13288,13290,13292,13294,13296],{"class":472,"line":5052},[470,13277,9265],{"class":4988},[470,13279,6522],{"class":5025},[470,13281,5028],{"class":4992},[470,13283,5956],{"class":5955},[470,13285,9314],{"class":4988},[470,13287,9317],{"class":5961},[470,13289,5109],{"class":4988},[470,13291,9322],{"class":5961},[470,13293,9325],{"class":4988},[470,13295,5967],{"class":5222},[470,13297,5131],{"class":4988},[470,13299,13300,13302,13304,13306,13308,13310,13312,13314,13316,13318,13320,13322,13324,13326,13328],{"class":472,"line":5069},[470,13301,9334],{"class":5222},[470,13303,6474],{"class":5226},[470,13305,5229],{"class":663},[470,13307,5982],{"class":4984},[470,13309,9317],{"class":4992},[470,13311,333],{"class":4988},[470,13313,9347],{"class":4992},[470,13315,333],{"class":4988},[470,13317,6522],{"class":4992},[470,13319,333],{"class":4988},[470,13321,8988],{"class":4992},[470,13323,333],{"class":4988},[470,13325,6531],{"class":5025},[470,13327,5028],{"class":5036},[470,13329,5031],{"class":4988},[470,13331,13332,13334,13336,13338,13340,13342,13344,13346,13348,13350,13352,13354,13356],{"class":472,"line":5092},[470,13333,9368],{"class":5036},[470,13335,1149],{"class":4988},[470,13337,6545],{"class":5025},[470,13339,5028],{"class":5036},[470,13341,8988],{"class":4992},[470,13343,333],{"class":4988},[470,13345,6563],{"class":4992},[470,13347,5109],{"class":4988},[470,13349,9322],{"class":4992},[470,13351,333],{"class":4988},[470,13353,9389],{"class":4992},[470,13355,5194],{"class":5036},[470,13357,5049],{"class":4988},[470,13359,13360,13362,13364],{"class":472,"line":5123},[470,13361,9398],{"class":4988},[470,13363,5194],{"class":5036},[470,13365,5009],{"class":4988},[470,13367,13368,13370,13372,13374,13376,13378,13380,13382,13384,13386,13388,13390,13392,13394,13396,13398,13400,13402],{"class":472,"line":5134},[470,13369,9407],{"class":4984},[470,13371,401],{"class":5036},[470,13373,9412],{"class":663},[470,13375,9415],{"class":4992},[470,13377,9418],{"class":5036},[470,13379,9421],{"class":4984},[470,13381,6215],{"class":663},[470,13383,9426],{"class":5025},[470,13385,5028],{"class":5036},[470,13387,9280],{"class":4988},[470,13389,9433],{"class":5036},[470,13391,1149],{"class":4988},[470,13393,500],{"class":499},[470,13395,9440],{"class":489},[470,13397,5006],{"class":499},[470,13399,4996],{"class":4988},[470,13401,5194],{"class":5036},[470,13403,5009],{"class":4988},[470,13405,13406,13408,13410,13412],{"class":472,"line":5151},[470,13407,9453],{"class":4984},[470,13409,6474],{"class":4992},[470,13411,9458],{"class":4988},[470,13413,13414],{"class":5326}," \u002F\u002F ¡Tipo inferido automáticamente!\n",[470,13416,13417,13419,13421],{"class":472,"line":5168},[470,13418,9466],{"class":4988},[470,13420,5194],{"class":4992},[470,13422,5049],{"class":4988},[470,13424,13425,13427,13429],{"class":472,"line":5182},[470,13426,5191],{"class":4988},[470,13428,5194],{"class":4992},[470,13430,5009],{"class":4988},[343,13432,13434],{"className":9481,"code":13433,"language":9483,"meta":349,"style":349},"\u003C!-- 3. Consumo en el Frontend Vue \u002F Nuxt con Cero Generación de Código -->\n\u003Cscript setup lang=\"ts\">\nconst { $trpc } = useNuxtApp();\nconst { data: profile, status } = await $trpc.users.getProfile.useQuery({\n  userId: \"usr_123\",\n});\n\n\u002F\u002F ¡profile.value.email tiene inferencia total y autocompletado!\n\u003C\u002Fscript>\n",[325,13435,13436,13441,13461,13479,13519,13533,13541,13545,13550],{"__ignoreMap":349},[470,13437,13438],{"class":472,"line":473},[470,13439,13440],{"class":5326},"\u003C!-- 3. Consumo en el Frontend Vue \u002F Nuxt con Cero Generación de Código -->\n",[470,13442,13443,13445,13447,13449,13451,13453,13455,13457,13459],{"class":472,"line":484},[470,13444,9495],{"class":4988},[470,13446,9499],{"class":9498},[470,13448,9503],{"class":9502},[470,13450,9506],{"class":9502},[470,13452,9509],{"class":4988},[470,13454,5006],{"class":499},[470,13456,9514],{"class":489},[470,13458,5006],{"class":499},[470,13460,9519],{"class":4988},[470,13462,13463,13465,13467,13469,13471,13473,13475,13477],{"class":472,"line":509},[470,13464,9524],{"class":5222},[470,13466,4989],{"class":4988},[470,13468,9529],{"class":5226},[470,13470,4996],{"class":4988},[470,13472,5229],{"class":663},[470,13474,9536],{"class":5025},[470,13476,6517],{"class":4992},[470,13478,5009],{"class":4988},[470,13480,13481,13483,13485,13487,13489,13491,13493,13495,13497,13499,13501,13503,13505,13507,13509,13511,13513,13515,13517],{"class":472,"line":528},[470,13482,9524],{"class":5222},[470,13484,4989],{"class":4988},[470,13486,9550],{"class":9549},[470,13488,1149],{"class":4988},[470,13490,9555],{"class":5226},[470,13492,5109],{"class":4988},[470,13494,9560],{"class":5226},[470,13496,4996],{"class":4988},[470,13498,5229],{"class":663},[470,13500,5982],{"class":4984},[470,13502,9529],{"class":4992},[470,13504,333],{"class":4988},[470,13506,8988],{"class":4992},[470,13508,333],{"class":4988},[470,13510,9577],{"class":4992},[470,13512,333],{"class":4988},[470,13514,9582],{"class":5025},[470,13516,5028],{"class":4992},[470,13518,5031],{"class":4988},[470,13520,13521,13523,13525,13527,13529,13531],{"class":472,"line":5052},[470,13522,9591],{"class":5036},[470,13524,1149],{"class":4988},[470,13526,500],{"class":499},[470,13528,9598],{"class":489},[470,13530,5006],{"class":499},[470,13532,5049],{"class":4988},[470,13534,13535,13537,13539],{"class":472,"line":5069},[470,13536,5191],{"class":4988},[470,13538,5194],{"class":4992},[470,13540,5009],{"class":4988},[470,13542,13543],{"class":472,"line":5092},[470,13544,5014],{"emptyLinePlaceholder":22},[470,13546,13547],{"class":472,"line":5123},[470,13548,13549],{"class":5326},"\u002F\u002F ¡profile.value.email tiene inferencia total y autocompletado!\n",[470,13551,13552,13554,13556],{"class":472,"line":5134},[470,13553,9624],{"class":4988},[470,13555,9499],{"class":9498},[470,13557,9519],{"class":4988},[4903,13559],{},[338,13561,13563],{"id":13562},"beneficios-prácticos-para-el-vibe-coding-y-la-ia","Beneficios Prácticos para el Vibe Coding y la IA",[321,13565,13566],{},"Al desarrollar con Cursor, GitHub Copilot o Claude Code, las definiciones de tipos funcionan como el \"prompt de contexto\" más potente posible. El agente de IA no necesita adivinar el formato de las respuestas HTTP ni inventar nombres de tablas: los tipos de TypeScript guían la generación del código correcto al primer intento.",[4903,13568],{},[338,13570,13572],{"id":13571},"conclusión","Conclusión",[321,13574,13575],{},"Adoptar TypeScript desde el día cero en tu SaaS ahorra semanas de depuración y hace posible un crecimiento sostenible de la base de código.",[321,13577,13578,13579,13581],{},"Si quieres saltarte la configuración manual de TypeScript, tRPC, Drizzle y Nuxt Layers y empezar directamente con una arquitectura de nivel enterprise lista para producción, conoce ",[455,13580,4900],{"href":5385}," y obtén acceso inmediato al código fuente completo.",[1945,13583,9655],{},{"title":349,"searchDepth":484,"depth":484,"links":13585},[13586,13587,13588,13589],{"id":12928,"depth":484,"text":12929},{"id":12965,"depth":484,"text":12966},{"id":13562,"depth":484,"text":13563},{"id":13571,"depth":484,"text":13572},"Aprende las mejores prácticas de TypeScript con tipado estricto de extremo a extremo, tRPC y Drizzle ORM en Nuxt 4 para acelerar el desarrollo de tu SaaS.","\u002Fimages\u002Fblog\u002Ftypescript-end-to-end-typesafety-es.svg",{},"\u002Fimages\u002Fblog\u002Ftypescript-end-to-end-typesafety-es.png","\u002Fblog\u002Fprimeros-pasos-typescript-nuxt-4",{"title":12910,"description":13590},"blog\u002Fprimeros-pasos-typescript-nuxt-4",{"en":9671,"pt-br":9672,"es":9673},"UwDHkR9L6R_xfE97Oe19b6wg4htp3UT73joOlpKzjgI",{"id":13600,"title":13601,"author":1994,"authorType":316,"body":13602,"category":14201,"date":5401,"description":14202,"extension":1971,"image":14203,"lang":3428,"meta":14204,"navigation":22,"ogImage":14205,"path":14206,"readTime":14207,"seo":14208,"stem":14209,"tags":14210,"translations":14214,"updated":316,"__hash__":14218},"blog\u002Fblog\u002Frealtime-websockets-durable-objects.md","Real-Time WebSockets Without Redis: Using Cloudflare Durable Objects in Nuxt 4",{"type":318,"value":13603,"toc":14195},[13604,13607,13610,13621,13630,13632,13636,13643,13650,13656,13658,13662,13665,13679,13884,13886,13890,13893,14171,14173,14177,14180,14185,14192],[321,13605,13606],{},"Building real-time features (such as instant notifications, collaborative cursors or live dashboards) has always been one of the biggest nightmares in serverless architectures.",[321,13608,13609],{},"Traditionally, the recipe for supporting WebSockets required:",[356,13611,13612,13615,13618],{},[359,13613,13614],{},"A cluster of always-on dedicated Node.js servers.",[359,13616,13617],{},"A Redis cluster with Pub\u002FSub to sync messages between instances.",[359,13619,13620],{},"A load balancer configured for persistent connections (Sticky Sessions).",[321,13622,13623,13624,13626,13627,333],{},"The monthly cost of this minimal infrastructure rarely fell below hundreds of dollars. ",[330,13625,4900],{}," solves this challenge using ",[330,13628,13629],{},"Cloudflare Durable Objects",[4903,13631],{},[338,13633,13635],{"id":13634},"what-are-cloudflare-durable-objects","What Are Cloudflare Durable Objects?",[321,13637,13638,13639,13642],{},"Durable Objects provide serverless compute with ",[330,13640,13641],{},"coordinated state",". Each object runs as a single instance on Cloudflare's global network, guaranteeing that all connections to the same \"channel\" or \"room\" reach the same memory process.",[321,13644,13645,13646,13649],{},"In addition, each Durable Object has its own ",[330,13647,13648],{},"embedded SQLite"," database, allowing message history to be persisted with sub-millisecond latency.",[343,13651,13654],{"className":13652,"code":13653,"language":348},[346],"[User A (Browser)] ──WebSockets───────> ┌────────────────────────────────┐\n                                        │ Cloudflare Durable Object      │\n[User B (Browser)] ──WebSockets───────> │ - Connection state in RAM      │\n                                        │ - Embedded SQLite database     │\n[Webhook \u002F Backend Worker] ──Fetch────> └────────────────────────────────┘\n",[325,13655,13653],{"__ignoreMap":349},[4903,13657],{},[338,13659,13661],{"id":13660},"robust-security-handshake-with-hmac-signed-tickets","Robust Security: Handshake with HMAC-Signed Tickets",[321,13663,13664],{},"WebSocket connections do not carry normal HTTP headers after the initial handshake. To guarantee maximum security against forged connections, Nuxfire uses cryptographically signed tickets:",[356,13666,13667,13670,13673,13676],{},[359,13668,13669],{},"The client makes an authenticated HTTP request asking for a connection ticket.",[359,13671,13672],{},"The server generates an HMAC-signed token using the application's secret key, valid for 60 seconds.",[359,13674,13675],{},"The client opens the WebSocket connection passing the ticket in the query string.",[359,13677,13678],{},"The Durable Object validates the signature before accepting the handshake.",[343,13680,13682],{"className":4975,"code":13681,"language":4977,"meta":349,"style":349},"\u002F\u002F Generating the signed ticket in the authentication endpoint\nexport function generateWebSocketTicket(userId: string, workspaceId: string) {\n  const expiresAt = Date.now() + 60 * 1000;\n  const payload = `${userId}:${workspaceId}:${expiresAt}`;\n  const signature = createHmac(\"sha256\", env.WS_SECRET).update(payload).digest(\"hex\");\n  return `${payload}:${signature}`;\n}\n",[325,13683,13684,13689,13719,13750,13793,13855,13880],{"__ignoreMap":349},[470,13685,13686],{"class":472,"line":473},[470,13687,13688],{"class":5326},"\u002F\u002F Generating the signed ticket in the authentication endpoint\n",[470,13690,13691,13693,13695,13698,13700,13702,13704,13706,13708,13711,13713,13715,13717],{"class":472,"line":484},[470,13692,5019],{"class":4984},[470,13694,6805],{"class":5222},[470,13696,13697],{"class":5025}," generateWebSocketTicket",[470,13699,5028],{"class":4988},[470,13701,9389],{"class":5961},[470,13703,1149],{"class":663},[470,13705,6819],{"class":6818},[470,13707,5109],{"class":4988},[470,13709,13710],{"class":5961}," workspaceId",[470,13712,1149],{"class":663},[470,13714,6819],{"class":6818},[470,13716,5194],{"class":4988},[470,13718,5131],{"class":4988},[470,13720,13721,13723,13726,13728,13730,13732,13734,13736,13739,13742,13745,13748],{"class":472,"line":509},[470,13722,5974],{"class":5222},[470,13724,13725],{"class":5226}," expiresAt",[470,13727,5229],{"class":663},[470,13729,6095],{"class":4992},[470,13731,333],{"class":4988},[470,13733,6100],{"class":5025},[470,13735,9296],{"class":5036},[470,13737,13738],{"class":663},"+",[470,13740,13741],{"class":6851}," 60",[470,13743,13744],{"class":663}," *",[470,13746,13747],{"class":6851}," 1000",[470,13749,5009],{"class":4988},[470,13751,13752,13754,13757,13759,13762,13766,13768,13770,13772,13774,13777,13779,13781,13783,13786,13788,13791],{"class":472,"line":528},[470,13753,5974],{"class":5222},[470,13755,13756],{"class":5226}," payload",[470,13758,5229],{"class":663},[470,13760,13761],{"class":499}," `",[470,13763,13765],{"class":13764},"slF1C","${",[470,13767,9389],{"class":4992},[470,13769,5191],{"class":13764},[470,13771,1149],{"class":489},[470,13773,13765],{"class":13764},[470,13775,13776],{"class":4992},"workspaceId",[470,13778,5191],{"class":13764},[470,13780,1149],{"class":489},[470,13782,13765],{"class":13764},[470,13784,13785],{"class":4992},"expiresAt",[470,13787,5191],{"class":13764},[470,13789,13790],{"class":499},"`",[470,13792,5009],{"class":4988},[470,13794,13795,13797,13800,13802,13805,13807,13809,13812,13814,13816,13818,13820,13823,13825,13827,13830,13832,13835,13837,13839,13842,13844,13846,13849,13851,13853],{"class":472,"line":5052},[470,13796,5974],{"class":5222},[470,13798,13799],{"class":5226}," signature",[470,13801,5229],{"class":663},[470,13803,13804],{"class":5025}," createHmac",[470,13806,5028],{"class":5036},[470,13808,5006],{"class":499},[470,13810,13811],{"class":489},"sha256",[470,13813,5006],{"class":499},[470,13815,5109],{"class":4988},[470,13817,6011],{"class":4992},[470,13819,333],{"class":4988},[470,13821,13822],{"class":5226},"WS_SECRET",[470,13824,5194],{"class":5036},[470,13826,333],{"class":4988},[470,13828,13829],{"class":5025},"update",[470,13831,5028],{"class":5036},[470,13833,13834],{"class":4992},"payload",[470,13836,5194],{"class":5036},[470,13838,333],{"class":4988},[470,13840,13841],{"class":5025},"digest",[470,13843,5028],{"class":5036},[470,13845,5006],{"class":499},[470,13847,13848],{"class":489},"hex",[470,13850,5006],{"class":499},[470,13852,5194],{"class":5036},[470,13854,5009],{"class":4988},[470,13856,13857,13859,13861,13863,13865,13867,13869,13871,13874,13876,13878],{"class":472,"line":5069},[470,13858,6123],{"class":4984},[470,13860,13761],{"class":499},[470,13862,13765],{"class":13764},[470,13864,13834],{"class":4992},[470,13866,5191],{"class":13764},[470,13868,1149],{"class":489},[470,13870,13765],{"class":13764},[470,13872,13873],{"class":4992},"signature",[470,13875,5191],{"class":13764},[470,13877,13790],{"class":499},[470,13879,5009],{"class":4988},[470,13881,13882],{"class":472,"line":5092},[470,13883,6887],{"class":4988},[4903,13885],{},[338,13887,13889],{"id":13888},"consuming-it-in-the-frontend-with-nuxt-composables","Consuming It in the Frontend with Nuxt Composables",[321,13891,13892],{},"On the Nuxt 4 client side, connecting to the team channel is as simple as using a native composable:",[343,13894,13896],{"className":9481,"code":13895,"language":9483,"meta":349,"style":349},"\u003Cscript setup lang=\"ts\">\nconst { messages, status, send } = useTeamWebSockets();\n\nfunction handleNotify() {\n  send({ type: \"TEAM_PING\", content: \"New task created on the board!\" });\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CUBadge :color=\"status === 'OPEN' ? 'success' : 'neutral'\">\n      {{ status === 'OPEN' ? 'Real Time Active' : 'Reconnecting...' }}\n    \u003C\u002FUBadge>\n    \u003Cdiv v-for=\"msg in messages\" :key=\"msg.id\">\n      {{ msg.content }}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[325,13897,13898,13918,13947,13951,13963,14003,14007,14015,14019,14028,14038,14095,14100,14109,14141,14146,14154,14163],{"__ignoreMap":349},[470,13899,13900,13902,13904,13906,13908,13910,13912,13914,13916],{"class":472,"line":473},[470,13901,9495],{"class":4988},[470,13903,9499],{"class":9498},[470,13905,9503],{"class":9502},[470,13907,9506],{"class":9502},[470,13909,9509],{"class":4988},[470,13911,5006],{"class":499},[470,13913,9514],{"class":489},[470,13915,5006],{"class":499},[470,13917,9519],{"class":4988},[470,13919,13920,13922,13924,13927,13929,13931,13933,13936,13938,13940,13943,13945],{"class":472,"line":484},[470,13921,9524],{"class":5222},[470,13923,4989],{"class":4988},[470,13925,13926],{"class":5226}," messages",[470,13928,5109],{"class":4988},[470,13930,9560],{"class":5226},[470,13932,5109],{"class":4988},[470,13934,13935],{"class":5226}," send",[470,13937,4996],{"class":4988},[470,13939,5229],{"class":663},[470,13941,13942],{"class":5025}," useTeamWebSockets",[470,13944,6517],{"class":4992},[470,13946,5009],{"class":4988},[470,13948,13949],{"class":472,"line":509},[470,13950,5014],{"emptyLinePlaceholder":22},[470,13952,13953,13956,13959,13961],{"class":472,"line":528},[470,13954,13955],{"class":5222},"function",[470,13957,13958],{"class":5025}," handleNotify",[470,13960,6517],{"class":4988},[470,13962,5131],{"class":4988},[470,13964,13965,13968,13970,13972,13974,13976,13978,13981,13983,13985,13988,13990,13992,13995,13997,13999,14001],{"class":472,"line":5052},[470,13966,13967],{"class":5025},"  send",[470,13969,5028],{"class":5036},[470,13971,9280],{"class":4988},[470,13973,9182],{"class":5036},[470,13975,1149],{"class":4988},[470,13977,500],{"class":499},[470,13979,13980],{"class":489},"TEAM_PING",[470,13982,5006],{"class":499},[470,13984,5109],{"class":4988},[470,13986,13987],{"class":5036}," content",[470,13989,1149],{"class":4988},[470,13991,500],{"class":499},[470,13993,13994],{"class":489},"New task created on the board!",[470,13996,5006],{"class":499},[470,13998,4996],{"class":4988},[470,14000,5194],{"class":5036},[470,14002,5009],{"class":4988},[470,14004,14005],{"class":472,"line":5069},[470,14006,6887],{"class":4988},[470,14008,14009,14011,14013],{"class":472,"line":5092},[470,14010,9624],{"class":4988},[470,14012,9499],{"class":9498},[470,14014,9519],{"class":4988},[470,14016,14017],{"class":472,"line":5123},[470,14018,5014],{"emptyLinePlaceholder":22},[470,14020,14021,14023,14026],{"class":472,"line":5134},[470,14022,9495],{"class":4988},[470,14024,14025],{"class":9498},"template",[470,14027,9519],{"class":4988},[470,14029,14030,14033,14036],{"class":472,"line":5151},[470,14031,14032],{"class":4988},"  \u003C",[470,14034,14035],{"class":9498},"div",[470,14037,9519],{"class":4988},[470,14039,14040,14043,14046,14049,14052,14054,14057,14060,14063,14066,14069,14072,14075,14077,14080,14082,14084,14086,14089,14091,14093],{"class":472,"line":5168},[470,14041,14042],{"class":4988},"    \u003C",[470,14044,14045],{"class":9498},"UBadge",[470,14047,14048],{"class":4988}," :",[470,14050,14051],{"class":9502},"color",[470,14053,9509],{"class":4988},[470,14055,5006],{"class":14056},"s00De",[470,14058,14059],{"class":4992},"status",[470,14061,14062],{"class":663}," ===",[470,14064,14065],{"class":499}," '",[470,14067,14068],{"class":489},"OPEN",[470,14070,14071],{"class":499},"'",[470,14073,14074],{"class":663}," ?",[470,14076,14065],{"class":499},[470,14078,14079],{"class":489},"success",[470,14081,14071],{"class":499},[470,14083,14048],{"class":663},[470,14085,14065],{"class":499},[470,14087,14088],{"class":489},"neutral",[470,14090,14071],{"class":499},[470,14092,5006],{"class":14056},[470,14094,9519],{"class":4988},[470,14096,14097],{"class":472,"line":5182},[470,14098,14099],{"class":4992},"      {{ status === 'OPEN' ? 'Real Time Active' : 'Reconnecting...' }}\n",[470,14101,14102,14105,14107],{"class":472,"line":5188},[470,14103,14104],{"class":4988},"    \u003C\u002F",[470,14106,14045],{"class":9498},[470,14108,9519],{"class":4988},[470,14110,14111,14113,14115,14118,14120,14122,14125,14127,14130,14132,14134,14137,14139],{"class":472,"line":6115},[470,14112,14042],{"class":4988},[470,14114,14035],{"class":9498},[470,14116,14117],{"class":9502}," v-for",[470,14119,9509],{"class":4988},[470,14121,5006],{"class":499},[470,14123,14124],{"class":489},"msg in messages",[470,14126,5006],{"class":499},[470,14128,14129],{"class":9502}," :key",[470,14131,9509],{"class":4988},[470,14133,5006],{"class":499},[470,14135,14136],{"class":489},"msg.id",[470,14138,5006],{"class":499},[470,14140,9519],{"class":4988},[470,14142,14143],{"class":472,"line":6120},[470,14144,14145],{"class":4992},"      {{ msg.content }}\n",[470,14147,14148,14150,14152],{"class":472,"line":6153},[470,14149,14104],{"class":4988},[470,14151,14035],{"class":9498},[470,14153,9519],{"class":4988},[470,14155,14156,14159,14161],{"class":472,"line":10926},[470,14157,14158],{"class":4988},"  \u003C\u002F",[470,14160,14035],{"class":9498},[470,14162,9519],{"class":4988},[470,14164,14165,14167,14169],{"class":472,"line":10931},[470,14166,9624],{"class":4988},[470,14168,14025],{"class":9498},[470,14170,9519],{"class":4988},[4903,14172],{},[338,14174,14176],{"id":14175},"conclusion-enterprise-grade-real-time-within-your-reach","Conclusion: Enterprise-Grade Real Time Within Your Reach",[321,14178,14179],{},"With Durable Objects, you completely eliminate the costs of Redis and Socket.io servers, taking advantage of Cloudflare's global network to deliver an ultra-fast experience to your users.",[321,14181,14182,14183,333],{},"This whole flow — from the backend workers to Nuxt's reactive composables — already comes 100% implemented and tested in ",[330,14184,4900],{},[321,14186,14187],{},[455,14188,14189],{"href":6318},[330,14190,14191],{},"Get your lifetime license and download the code right now →",[1945,14193,14194],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .slF1C, html code.shiki .slF1C{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#F92672}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sHsBP, html code.shiki .sHsBP{--shiki-light:#E53935;--shiki-default:#22863A;--shiki-dark:#85E89D;--shiki-sepia:#F92672}html pre.shiki code .s_lYk, html code.shiki .s_lYk{--shiki-light:#9C3EDA;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .s00De, html code.shiki .s00De{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#F8F8F2}",{"title":349,"searchDepth":484,"depth":484,"links":14196},[14197,14198,14199,14200],{"id":13634,"depth":484,"text":13635},{"id":13660,"depth":484,"text":13661},{"id":13888,"depth":484,"text":13889},{"id":14175,"depth":484,"text":14176},"Real-Time & WebSockets","Discover how to build instant notifications and multi-user collaboration without dedicated Socket.io servers or expensive Redis instances, using Durable Objects.","\u002Fimages\u002Fblog\u002Fwebsockets-durable-objects-en.svg",{},"\u002Fimages\u002Fblog\u002Fwebsockets-durable-objects-en.png","\u002Fblog\u002Frealtime-websockets-durable-objects","6 min",{"title":13601,"description":14202},"blog\u002Frealtime-websockets-durable-objects",[78,14211,14212,14213],"Durable Objects","Cloudflare","Real-Time",{"en":14215,"pt-br":14216,"es":14217},"realtime-websockets-durable-objects","websockets-durable-objects-tempo-real","websockets-durable-objects-tiempo-real","BcxUUs6dxhgsJDCN4XXh3MyQB6YpJiP_VDm09IHqv1E",{"id":14220,"title":14221,"author":8894,"authorType":8895,"body":14222,"category":8902,"date":5401,"description":14901,"extension":1971,"image":14902,"lang":3428,"meta":14903,"navigation":22,"ogImage":14904,"path":14905,"readTime":9667,"seo":14906,"stem":14907,"tags":316,"translations":14908,"updated":316,"__hash__":14909},"blog\u002Fblog\u002Ftypescript-nuxt-4-getting-started-guide.md","Getting Started with TypeScript in Nuxt 4: A Complete Guide for SaaS",{"type":318,"value":14223,"toc":14895},[14224,14229,14235,14237,14241,14244,14272,14274,14278,14281,14519,14742,14869,14871,14875,14878,14880,14884,14887,14893],[321,14225,14226,14228],{},[330,14227,8902],{}," has become an indispensable requirement in modern software development, especially when building critical SaaS applications where runtime errors cost customers and reputation.",[321,14230,14231,14232,14234],{},"In the ",[330,14233,4900],{}," ecosystem, TypeScript is not just a linter or a superficial type check: it is the fundamental foundation that connects the PostgreSQL database to the Vue 3 client, through the tRPC API layer and the SST v4 infrastructure.",[4903,14236],{},[338,14238,14240],{"id":14239},"why-is-strict-typing-critical-in-saas","Why Is Strict Typing Critical in SaaS?",[321,14242,14243],{},"Building scalable software requires predictability. When multiple developers (or AI assistants like Cursor and Claude Code) collaborate on the same code, strict typing guarantees:",[356,14245,14246,14252,14260,14266],{},[359,14247,14248,14251],{},[330,14249,14250],{},"Fearless Refactoring",": Rename database fields or change function signatures and the compiler points to exactly which screens need adjusting.",[359,14253,14254,8932,14257,14259],{},[330,14255,14256],{},"Elimination of Entire Classes of Bugs",[325,14258,8935],{}," and accesses to nonexistent properties are caught before the commit.",[359,14261,14262,14265],{},[330,14263,14264],{},"Smart IDE Autocompletion",": Multiplied productivity with instant suggestions for parameters, return values and utility types.",[359,14267,14268,14271],{},[330,14269,14270],{},"Perfect Alignment with AI Tools",": LLMs produce substantially better code when guided by strict interface definitions.",[4903,14273],{},[338,14275,14277],{"id":14276},"the-end-to-end-typing-flow-in-nuxfire","The End-to-End Typing Flow in Nuxfire",[321,14279,14280],{},"Nuxfire's architecture implements a complete type inference pipeline across 3 layers:",[343,14282,14284],{"className":4975,"code":14283,"language":4977,"meta":349,"style":349},"\u002F\u002F 1. Database Schema (Drizzle ORM)\nexport const users = pgTable(\"users\", {\n  id: text(\"id\").primaryKey(),\n  email: text(\"email\").notNull().unique(),\n  role: text(\"role\", { enum: [\"owner\", \"admin\", \"member\"] }).notNull(),\n  createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n});\n\nexport type User = typeof users.$inferSelect;\nexport type NewUser = typeof users.$inferInsert;\n",[325,14285,14286,14291,14315,14341,14373,14435,14467,14475,14479,14499],{"__ignoreMap":349},[470,14287,14288],{"class":472,"line":473},[470,14289,14290],{"class":5326},"\u002F\u002F 1. Database Schema (Drizzle ORM)\n",[470,14292,14293,14295,14297,14299,14301,14303,14305,14307,14309,14311,14313],{"class":472,"line":484},[470,14294,5019],{"class":4984},[470,14296,5223],{"class":5222},[470,14298,8976],{"class":5226},[470,14300,5229],{"class":663},[470,14302,8981],{"class":5025},[470,14304,5028],{"class":4992},[470,14306,5006],{"class":499},[470,14308,8988],{"class":489},[470,14310,5006],{"class":499},[470,14312,5109],{"class":4988},[470,14314,5131],{"class":4988},[470,14316,14317,14319,14321,14323,14325,14327,14329,14331,14333,14335,14337,14339],{"class":472,"line":509},[470,14318,8999],{"class":5036},[470,14320,1149],{"class":4988},[470,14322,9004],{"class":5025},[470,14324,5028],{"class":4992},[470,14326,5006],{"class":499},[470,14328,6563],{"class":489},[470,14330,5006],{"class":499},[470,14332,5194],{"class":4992},[470,14334,333],{"class":4988},[470,14336,9019],{"class":5025},[470,14338,6517],{"class":4992},[470,14340,5049],{"class":4988},[470,14342,14343,14345,14347,14349,14351,14353,14355,14357,14359,14361,14363,14365,14367,14369,14371],{"class":472,"line":528},[470,14344,9028],{"class":5036},[470,14346,1149],{"class":4988},[470,14348,9004],{"class":5025},[470,14350,5028],{"class":4992},[470,14352,5006],{"class":499},[470,14354,9039],{"class":489},[470,14356,5006],{"class":499},[470,14358,5194],{"class":4992},[470,14360,333],{"class":4988},[470,14362,9048],{"class":5025},[470,14364,6517],{"class":4992},[470,14366,333],{"class":4988},[470,14368,9055],{"class":5025},[470,14370,6517],{"class":4992},[470,14372,5049],{"class":4988},[470,14374,14375,14377,14379,14381,14383,14385,14387,14389,14391,14393,14395,14397,14399,14401,14403,14405,14407,14409,14411,14413,14415,14417,14419,14421,14423,14425,14427,14429,14431,14433],{"class":472,"line":5052},[470,14376,9064],{"class":5036},[470,14378,1149],{"class":4988},[470,14380,9004],{"class":5025},[470,14382,5028],{"class":4992},[470,14384,5006],{"class":499},[470,14386,9075],{"class":489},[470,14388,5006],{"class":499},[470,14390,5109],{"class":4988},[470,14392,4989],{"class":4988},[470,14394,9084],{"class":5036},[470,14396,1149],{"class":4988},[470,14398,5077],{"class":4992},[470,14400,5006],{"class":499},[470,14402,9093],{"class":489},[470,14404,5006],{"class":499},[470,14406,5109],{"class":4988},[470,14408,500],{"class":499},[470,14410,5295],{"class":489},[470,14412,5006],{"class":499},[470,14414,5109],{"class":4988},[470,14416,500],{"class":499},[470,14418,9110],{"class":489},[470,14420,5006],{"class":499},[470,14422,9115],{"class":4992},[470,14424,5191],{"class":4988},[470,14426,5194],{"class":4992},[470,14428,333],{"class":4988},[470,14430,9048],{"class":5025},[470,14432,6517],{"class":4992},[470,14434,5049],{"class":4988},[470,14436,14437,14439,14441,14443,14445,14447,14449,14451,14453,14455,14457,14459,14461,14463,14465],{"class":472,"line":5069},[470,14438,9132],{"class":5036},[470,14440,1149],{"class":4988},[470,14442,9137],{"class":5025},[470,14444,5028],{"class":4992},[470,14446,5006],{"class":499},[470,14448,9144],{"class":489},[470,14450,5006],{"class":499},[470,14452,5194],{"class":4992},[470,14454,333],{"class":4988},[470,14456,9153],{"class":5025},[470,14458,6517],{"class":4992},[470,14460,333],{"class":4988},[470,14462,9048],{"class":5025},[470,14464,6517],{"class":4992},[470,14466,5049],{"class":4988},[470,14468,14469,14471,14473],{"class":472,"line":5092},[470,14470,5191],{"class":4988},[470,14472,5194],{"class":4992},[470,14474,5009],{"class":4988},[470,14476,14477],{"class":472,"line":5123},[470,14478,5014],{"emptyLinePlaceholder":22},[470,14480,14481,14483,14485,14487,14489,14491,14493,14495,14497],{"class":472,"line":5134},[470,14482,5019],{"class":4984},[470,14484,9182],{"class":5222},[470,14486,9186],{"class":9185},[470,14488,5229],{"class":663},[470,14490,9191],{"class":663},[470,14492,8976],{"class":4992},[470,14494,333],{"class":4988},[470,14496,9198],{"class":4992},[470,14498,5009],{"class":4988},[470,14500,14501,14503,14505,14507,14509,14511,14513,14515,14517],{"class":472,"line":5151},[470,14502,5019],{"class":4984},[470,14504,9182],{"class":5222},[470,14506,9209],{"class":9185},[470,14508,5229],{"class":663},[470,14510,9191],{"class":663},[470,14512,8976],{"class":4992},[470,14514,333],{"class":4988},[470,14516,9220],{"class":4992},[470,14518,5009],{"class":4988},[343,14520,14522],{"className":4975,"code":14521,"language":4977,"meta":349,"style":349},"\u002F\u002F 2. Typed API Procedure (tRPC v11)\nexport const usersRouter = router({\n  getProfile: protectedProcedure\n    .input(z.object({ userId: z.string() }))\n    .query(async ({ ctx, input }) => {\n      const user = await ctx.db.query.users.findFirst({\n        where: eq(users.id, input.userId),\n      });\n      if (!user) throw new TRPCError({ code: \"NOT_FOUND\" });\n      return user; \u002F\u002F Type inferred automatically!\n    }),\n});\n",[325,14523,14524,14529,14545,14553,14585,14609,14641,14669,14677,14715,14726,14734],{"__ignoreMap":349},[470,14525,14526],{"class":472,"line":473},[470,14527,14528],{"class":5326},"\u002F\u002F 2. Typed API Procedure (tRPC v11)\n",[470,14530,14531,14533,14535,14537,14539,14541,14543],{"class":472,"line":484},[470,14532,5019],{"class":4984},[470,14534,5223],{"class":5222},[470,14536,9241],{"class":5226},[470,14538,5229],{"class":663},[470,14540,9246],{"class":5025},[470,14542,5028],{"class":4992},[470,14544,5031],{"class":4988},[470,14546,14547,14549,14551],{"class":472,"line":509},[470,14548,9255],{"class":5036},[470,14550,1149],{"class":4988},[470,14552,9260],{"class":4992},[470,14554,14555,14557,14559,14561,14563,14565,14567,14569,14571,14573,14575,14577,14579,14581,14583],{"class":472,"line":528},[470,14556,9265],{"class":4988},[470,14558,1863],{"class":5025},[470,14560,9270],{"class":4992},[470,14562,333],{"class":4988},[470,14564,9275],{"class":5025},[470,14566,5028],{"class":4992},[470,14568,9280],{"class":4988},[470,14570,9283],{"class":5036},[470,14572,1149],{"class":4988},[470,14574,9288],{"class":4992},[470,14576,333],{"class":4988},[470,14578,9293],{"class":5025},[470,14580,9296],{"class":4992},[470,14582,5191],{"class":4988},[470,14584,9301],{"class":4992},[470,14586,14587,14589,14591,14593,14595,14597,14599,14601,14603,14605,14607],{"class":472,"line":5052},[470,14588,9265],{"class":4988},[470,14590,6522],{"class":5025},[470,14592,5028],{"class":4992},[470,14594,5956],{"class":5955},[470,14596,9314],{"class":4988},[470,14598,9317],{"class":5961},[470,14600,5109],{"class":4988},[470,14602,9322],{"class":5961},[470,14604,9325],{"class":4988},[470,14606,5967],{"class":5222},[470,14608,5131],{"class":4988},[470,14610,14611,14613,14615,14617,14619,14621,14623,14625,14627,14629,14631,14633,14635,14637,14639],{"class":472,"line":5069},[470,14612,9334],{"class":5222},[470,14614,6474],{"class":5226},[470,14616,5229],{"class":663},[470,14618,5982],{"class":4984},[470,14620,9317],{"class":4992},[470,14622,333],{"class":4988},[470,14624,9347],{"class":4992},[470,14626,333],{"class":4988},[470,14628,6522],{"class":4992},[470,14630,333],{"class":4988},[470,14632,8988],{"class":4992},[470,14634,333],{"class":4988},[470,14636,6531],{"class":5025},[470,14638,5028],{"class":5036},[470,14640,5031],{"class":4988},[470,14642,14643,14645,14647,14649,14651,14653,14655,14657,14659,14661,14663,14665,14667],{"class":472,"line":5092},[470,14644,9368],{"class":5036},[470,14646,1149],{"class":4988},[470,14648,6545],{"class":5025},[470,14650,5028],{"class":5036},[470,14652,8988],{"class":4992},[470,14654,333],{"class":4988},[470,14656,6563],{"class":4992},[470,14658,5109],{"class":4988},[470,14660,9322],{"class":4992},[470,14662,333],{"class":4988},[470,14664,9389],{"class":4992},[470,14666,5194],{"class":5036},[470,14668,5049],{"class":4988},[470,14670,14671,14673,14675],{"class":472,"line":5123},[470,14672,9398],{"class":4988},[470,14674,5194],{"class":5036},[470,14676,5009],{"class":4988},[470,14678,14679,14681,14683,14685,14687,14689,14691,14693,14695,14697,14699,14701,14703,14705,14707,14709,14711,14713],{"class":472,"line":5134},[470,14680,9407],{"class":4984},[470,14682,401],{"class":5036},[470,14684,9412],{"class":663},[470,14686,9415],{"class":4992},[470,14688,9418],{"class":5036},[470,14690,9421],{"class":4984},[470,14692,6215],{"class":663},[470,14694,9426],{"class":5025},[470,14696,5028],{"class":5036},[470,14698,9280],{"class":4988},[470,14700,9433],{"class":5036},[470,14702,1149],{"class":4988},[470,14704,500],{"class":499},[470,14706,9440],{"class":489},[470,14708,5006],{"class":499},[470,14710,4996],{"class":4988},[470,14712,5194],{"class":5036},[470,14714,5009],{"class":4988},[470,14716,14717,14719,14721,14723],{"class":472,"line":5151},[470,14718,9453],{"class":4984},[470,14720,6474],{"class":4992},[470,14722,9458],{"class":4988},[470,14724,14725],{"class":5326}," \u002F\u002F Type inferred automatically!\n",[470,14727,14728,14730,14732],{"class":472,"line":5168},[470,14729,9466],{"class":4988},[470,14731,5194],{"class":4992},[470,14733,5049],{"class":4988},[470,14735,14736,14738,14740],{"class":472,"line":5182},[470,14737,5191],{"class":4988},[470,14739,5194],{"class":4992},[470,14741,5009],{"class":4988},[343,14743,14745],{"className":9481,"code":14744,"language":9483,"meta":349,"style":349},"\u003C!-- 3. Consumption in the Vue \u002F Nuxt Frontend with Zero Code Generation -->\n\u003Cscript setup lang=\"ts\">\nconst { $trpc } = useNuxtApp();\nconst { data: profile, status } = await $trpc.users.getProfile.useQuery({\n  userId: \"usr_123\",\n});\n\n\u002F\u002F profile.value.email has full inference and autocompletion!\n\u003C\u002Fscript>\n",[325,14746,14747,14752,14772,14790,14830,14844,14852,14856,14861],{"__ignoreMap":349},[470,14748,14749],{"class":472,"line":473},[470,14750,14751],{"class":5326},"\u003C!-- 3. Consumption in the Vue \u002F Nuxt Frontend with Zero Code Generation -->\n",[470,14753,14754,14756,14758,14760,14762,14764,14766,14768,14770],{"class":472,"line":484},[470,14755,9495],{"class":4988},[470,14757,9499],{"class":9498},[470,14759,9503],{"class":9502},[470,14761,9506],{"class":9502},[470,14763,9509],{"class":4988},[470,14765,5006],{"class":499},[470,14767,9514],{"class":489},[470,14769,5006],{"class":499},[470,14771,9519],{"class":4988},[470,14773,14774,14776,14778,14780,14782,14784,14786,14788],{"class":472,"line":509},[470,14775,9524],{"class":5222},[470,14777,4989],{"class":4988},[470,14779,9529],{"class":5226},[470,14781,4996],{"class":4988},[470,14783,5229],{"class":663},[470,14785,9536],{"class":5025},[470,14787,6517],{"class":4992},[470,14789,5009],{"class":4988},[470,14791,14792,14794,14796,14798,14800,14802,14804,14806,14808,14810,14812,14814,14816,14818,14820,14822,14824,14826,14828],{"class":472,"line":528},[470,14793,9524],{"class":5222},[470,14795,4989],{"class":4988},[470,14797,9550],{"class":9549},[470,14799,1149],{"class":4988},[470,14801,9555],{"class":5226},[470,14803,5109],{"class":4988},[470,14805,9560],{"class":5226},[470,14807,4996],{"class":4988},[470,14809,5229],{"class":663},[470,14811,5982],{"class":4984},[470,14813,9529],{"class":4992},[470,14815,333],{"class":4988},[470,14817,8988],{"class":4992},[470,14819,333],{"class":4988},[470,14821,9577],{"class":4992},[470,14823,333],{"class":4988},[470,14825,9582],{"class":5025},[470,14827,5028],{"class":4992},[470,14829,5031],{"class":4988},[470,14831,14832,14834,14836,14838,14840,14842],{"class":472,"line":5052},[470,14833,9591],{"class":5036},[470,14835,1149],{"class":4988},[470,14837,500],{"class":499},[470,14839,9598],{"class":489},[470,14841,5006],{"class":499},[470,14843,5049],{"class":4988},[470,14845,14846,14848,14850],{"class":472,"line":5069},[470,14847,5191],{"class":4988},[470,14849,5194],{"class":4992},[470,14851,5009],{"class":4988},[470,14853,14854],{"class":472,"line":5092},[470,14855,5014],{"emptyLinePlaceholder":22},[470,14857,14858],{"class":472,"line":5123},[470,14859,14860],{"class":5326},"\u002F\u002F profile.value.email has full inference and autocompletion!\n",[470,14862,14863,14865,14867],{"class":472,"line":5134},[470,14864,9624],{"class":4988},[470,14866,9499],{"class":9498},[470,14868,9519],{"class":4988},[4903,14870],{},[338,14872,14874],{"id":14873},"practical-benefits-for-vibe-coding-and-ai","Practical Benefits for Vibe Coding and AI",[321,14876,14877],{},"When developing with Cursor, GitHub Copilot or Claude Code, type definitions work as the most powerful \"context prompt\" possible. The AI agent does not need to guess the shape of HTTP responses or invent table names: the TypeScript types guide the generation of the right code on the first attempt.",[4903,14879],{},[338,14881,14883],{"id":14882},"conclusion","Conclusion",[321,14885,14886],{},"Adopting TypeScript from day zero in your SaaS saves weeks of debugging and makes sustainable growth of the codebase possible.",[321,14888,14889,14890,14892],{},"If you want to skip the manual setup of TypeScript, tRPC, Drizzle and Nuxt Layers and start directly with a production-ready, enterprise-grade architecture, get to know ",[455,14891,4900],{"href":6318}," and get immediate access to the complete source code.",[1945,14894,9655],{},{"title":349,"searchDepth":484,"depth":484,"links":14896},[14897,14898,14899,14900],{"id":14239,"depth":484,"text":14240},{"id":14276,"depth":484,"text":14277},{"id":14873,"depth":484,"text":14874},{"id":14882,"depth":484,"text":14883},"Learn TypeScript best practices with end-to-end strict typing, tRPC and Drizzle ORM in Nuxt 4 to speed up the development of your SaaS.","\u002Fimages\u002Fblog\u002Ftypescript-end-to-end-typesafety-en.svg",{},"\u002Fimages\u002Fblog\u002Ftypescript-end-to-end-typesafety-en.png","\u002Fblog\u002Ftypescript-nuxt-4-getting-started-guide",{"title":14221,"description":14901},"blog\u002Ftypescript-nuxt-4-getting-started-guide",{"en":9671,"pt-br":9672,"es":9673},"X5aDikjV9vQDvRidRVuh40le9Jd5y_UeaE6Is6FbCX4",{"id":14911,"title":14912,"author":1994,"authorType":316,"body":14913,"category":15436,"date":5401,"description":15437,"extension":1971,"image":15438,"lang":3428,"meta":15439,"navigation":22,"ogImage":15440,"path":15441,"readTime":5407,"seo":15442,"stem":15443,"tags":15444,"translations":15446,"updated":316,"__hash__":15450},"blog\u002Fblog\u002Fvibe-coding-ai-cursor-claude-code-saas.md","Vibe Coding with AI: How Strict Typing and Nuxt Layers Supercharge Cursor and Claude Code",{"type":318,"value":14914,"toc":15428},[14915,14932,14938,14941,14947,14949,14953,14956,14961,14965,14979,15136,15144,15146,15150,15155,15171,15174,15176,15180,15186,15386,15388,15392,15395,15415,15418,15425],[321,14916,14917,14918,14921,14922,965,14925,2074,14928,14931],{},"The term ",[330,14919,14920],{},"\"Vibe Coding\""," has become the biggest trend in software development in 2026. Experienced developers and technical founders use intelligent assistants like ",[330,14923,14924],{},"Cursor",[330,14926,14927],{},"Claude Code",[330,14929,14930],{},"v0"," to write code at speeds never imagined before.",[321,14933,14934,14935,333],{},"However, almost everyone who tries to build a complex SaaS with prompts alone runs into the same obstacle: ",[330,14936,14937],{},"code entropy",[321,14939,14940],{},"After a few iterations, the AI starts to hallucinate variables that do not exist, recreates functions already implemented in other files, breaks entire screens and introduces silent database vulnerabilities.",[321,14942,14943,14944,14946],{},"Why does this happen? And how does the ",[330,14945,4900],{}," architecture solve this problem?",[4903,14948],{},[338,14950,14952],{"id":14951},"the-secret-is-not-the-prompt-it-is-the-structural-context","The Secret Is Not the Prompt, It Is the Structural Context",[321,14954,14955],{},"Large language models (LLMs) are only as good as the context they receive in their attention window. If your project is a confusing monolith with hundreds of interdependent files and no strict typing, the model has to \"guess\" the relationships between the parts.",[321,14957,14958,14960],{},[330,14959,4900],{}," was designed specifically to be the ideal foundation for AI tools, combining three architectural pillars:",[6406,14962,14964],{"id":14963},"_1-end-to-end-strict-typing-with-typescript-trpc","1. End-to-End Strict Typing with TypeScript & tRPC",[321,14966,14967,14968,14971,14972,14975,14976,1149],{},"In Nuxfire, there are no ",[325,14969,14970],{},"any"," objects or untyped payloads. Every API route defines its input and output with ",[330,14973,14974],{},"Zod"," schemas through ",[330,14977,14978],{},"tRPC",[343,14980,14982],{"className":4975,"code":14981,"language":4977,"meta":349,"style":349},"export const billingRouter = router({\n  createCheckoutSession: protectedProcedure\n    .input(z.object({\n      priceId: z.string(),\n      teamId: z.string()\n    }))\n    .mutation(async ({ input, ctx }) => {\n      \u002F\u002F Cursor knows exactly which properties exist on 'input' and 'ctx'\n      return createStripeSession(input.teamId, input.priceId);\n    })\n});\n",[325,14983,14984,15001,15010,15026,15043,15058,15064,15089,15094,15122,15128],{"__ignoreMap":349},[470,14985,14986,14988,14990,14993,14995,14997,14999],{"class":472,"line":473},[470,14987,5019],{"class":4984},[470,14989,5223],{"class":5222},[470,14991,14992],{"class":5226}," billingRouter",[470,14994,5229],{"class":663},[470,14996,9246],{"class":5025},[470,14998,5028],{"class":4992},[470,15000,5031],{"class":4988},[470,15002,15003,15006,15008],{"class":472,"line":484},[470,15004,15005],{"class":5036},"  createCheckoutSession",[470,15007,1149],{"class":4988},[470,15009,9260],{"class":4992},[470,15011,15012,15014,15016,15018,15020,15022,15024],{"class":472,"line":509},[470,15013,9265],{"class":4988},[470,15015,1863],{"class":5025},[470,15017,9270],{"class":4992},[470,15019,333],{"class":4988},[470,15021,9275],{"class":5025},[470,15023,5028],{"class":4992},[470,15025,5031],{"class":4988},[470,15027,15028,15031,15033,15035,15037,15039,15041],{"class":472,"line":528},[470,15029,15030],{"class":5036},"      priceId",[470,15032,1149],{"class":4988},[470,15034,9288],{"class":4992},[470,15036,333],{"class":4988},[470,15038,9293],{"class":5025},[470,15040,6517],{"class":4992},[470,15042,5049],{"class":4988},[470,15044,15045,15048,15050,15052,15054,15056],{"class":472,"line":5052},[470,15046,15047],{"class":5036},"      teamId",[470,15049,1149],{"class":4988},[470,15051,9288],{"class":4992},[470,15053,333],{"class":4988},[470,15055,9293],{"class":5025},[470,15057,6103],{"class":4992},[470,15059,15060,15062],{"class":472,"line":5069},[470,15061,9466],{"class":4988},[470,15063,9301],{"class":4992},[470,15065,15066,15068,15071,15073,15075,15077,15079,15081,15083,15085,15087],{"class":472,"line":5092},[470,15067,9265],{"class":4988},[470,15069,15070],{"class":5025},"mutation",[470,15072,5028],{"class":4992},[470,15074,5956],{"class":5955},[470,15076,9314],{"class":4988},[470,15078,9322],{"class":5961},[470,15080,5109],{"class":4988},[470,15082,9317],{"class":5961},[470,15084,9325],{"class":4988},[470,15086,5967],{"class":5222},[470,15088,5131],{"class":4988},[470,15090,15091],{"class":472,"line":5123},[470,15092,15093],{"class":5326},"      \u002F\u002F Cursor knows exactly which properties exist on 'input' and 'ctx'\n",[470,15095,15096,15098,15101,15103,15105,15107,15109,15111,15113,15115,15118,15120],{"class":472,"line":5134},[470,15097,9453],{"class":4984},[470,15099,15100],{"class":5025}," createStripeSession",[470,15102,5028],{"class":5036},[470,15104,1863],{"class":4992},[470,15106,333],{"class":4988},[470,15108,10797],{"class":4992},[470,15110,5109],{"class":4988},[470,15112,9322],{"class":4992},[470,15114,333],{"class":4988},[470,15116,15117],{"class":4992},"priceId",[470,15119,5194],{"class":5036},[470,15121,5009],{"class":4988},[470,15123,15124,15126],{"class":472,"line":5151},[470,15125,9466],{"class":4988},[470,15127,6271],{"class":4992},[470,15129,15130,15132,15134],{"class":472,"line":5168},[470,15131,5191],{"class":4988},[470,15133,5194],{"class":4992},[470,15135,5009],{"class":4988},[321,15137,15138,15139,15143],{},"When you ask Cursor or Claude Code: ",[15140,15141,15142],"em",{},"\"Create a button in the dashboard that calls the checkout procedure\"",", the model reads the tRPC type definitions and autocompletes the correct parameters on the first attempt, without inventing false properties.",[4903,15145],{},[6406,15147,15149],{"id":15148},"_2-nuxt-layers-clear-domain-boundaries","2. Nuxt Layers: Clear Domain Boundaries",[321,15151,5904,15152,15154],{},[330,15153,4896],{},", each functional area of the product is confined to its own directory:",[424,15156,15157,15164],{},[359,15158,15159,15160,15163],{},"Changing the team member flow? Only files in ",[325,15161,15162],{},"layers\u002Fteams\u002F"," are loaded.",[359,15165,15166,15167,15170],{},"Modifying theme styles? The ",[325,15168,15169],{},"packages\u002Ftokens"," file centralizes everything.",[321,15172,15173],{},"This compartmentalization reduces the amount of context needed and stops the AI from changing unrelated files by mistake.",[4903,15175],{},[6406,15177,15179],{"id":15178},"_3-structured-schemas-with-drizzle-orm","3. Structured Schemas with Drizzle ORM",[321,15181,15182,15183,15185],{},"Mapping database relationships with legacy ORMs often produces hallucinations in SQL migrations. ",[330,15184,6748],{}," uses pure declarative TypeScript, making it easier for the AI to understand foreign keys, indexes and integrity constraints with total precision:",[343,15187,15189],{"className":4975,"code":15188,"language":4977,"meta":349,"style":349},"export const subscriptions = pgTable(\"subscriptions\", {\n  id: text(\"id\").primaryKey(),\n  teamId: text(\"team_id\").notNull().references(() => teams.id),\n  status: text(\"status\", { enum: [\"ACTIVE\", \"PAST_DUE\", \"CANCELED\"] }).notNull(),\n  currentPeriodEnd: timestamp(\"current_period_end\").notNull()\n});\n",[325,15190,15191,15217,15243,15286,15352,15378],{"__ignoreMap":349},[470,15192,15193,15195,15197,15200,15202,15204,15206,15208,15211,15213,15215],{"class":472,"line":473},[470,15194,5019],{"class":4984},[470,15196,5223],{"class":5222},[470,15198,15199],{"class":5226}," subscriptions",[470,15201,5229],{"class":663},[470,15203,8981],{"class":5025},[470,15205,5028],{"class":4992},[470,15207,5006],{"class":499},[470,15209,15210],{"class":489},"subscriptions",[470,15212,5006],{"class":499},[470,15214,5109],{"class":4988},[470,15216,5131],{"class":4988},[470,15218,15219,15221,15223,15225,15227,15229,15231,15233,15235,15237,15239,15241],{"class":472,"line":484},[470,15220,8999],{"class":5036},[470,15222,1149],{"class":4988},[470,15224,9004],{"class":5025},[470,15226,5028],{"class":4992},[470,15228,5006],{"class":499},[470,15230,6563],{"class":489},[470,15232,5006],{"class":499},[470,15234,5194],{"class":4992},[470,15236,333],{"class":4988},[470,15238,9019],{"class":5025},[470,15240,6517],{"class":4992},[470,15242,5049],{"class":4988},[470,15244,15245,15247,15249,15251,15253,15255,15257,15259,15261,15263,15265,15267,15269,15271,15273,15275,15277,15279,15281,15284],{"class":472,"line":509},[470,15246,10437],{"class":5036},[470,15248,1149],{"class":4988},[470,15250,9004],{"class":5025},[470,15252,5028],{"class":4992},[470,15254,5006],{"class":499},[470,15256,10184],{"class":489},[470,15258,5006],{"class":499},[470,15260,5194],{"class":4992},[470,15262,333],{"class":4988},[470,15264,9048],{"class":5025},[470,15266,6517],{"class":4992},[470,15268,333],{"class":4988},[470,15270,10462],{"class":5025},[470,15272,5028],{"class":4992},[470,15274,6517],{"class":4988},[470,15276,5967],{"class":5222},[470,15278,10207],{"class":4992},[470,15280,333],{"class":4988},[470,15282,15283],{"class":4992},"id)",[470,15285,5049],{"class":4988},[470,15287,15288,15291,15293,15295,15297,15299,15301,15303,15305,15307,15309,15311,15313,15315,15318,15320,15322,15324,15327,15329,15331,15333,15336,15338,15340,15342,15344,15346,15348,15350],{"class":472,"line":528},[470,15289,15290],{"class":5036},"  status",[470,15292,1149],{"class":4988},[470,15294,9004],{"class":5025},[470,15296,5028],{"class":4992},[470,15298,5006],{"class":499},[470,15300,14059],{"class":489},[470,15302,5006],{"class":499},[470,15304,5109],{"class":4988},[470,15306,4989],{"class":4988},[470,15308,9084],{"class":5036},[470,15310,1149],{"class":4988},[470,15312,5077],{"class":4992},[470,15314,5006],{"class":499},[470,15316,15317],{"class":489},"ACTIVE",[470,15319,5006],{"class":499},[470,15321,5109],{"class":4988},[470,15323,500],{"class":499},[470,15325,15326],{"class":489},"PAST_DUE",[470,15328,5006],{"class":499},[470,15330,5109],{"class":4988},[470,15332,500],{"class":499},[470,15334,15335],{"class":489},"CANCELED",[470,15337,5006],{"class":499},[470,15339,9115],{"class":4992},[470,15341,5191],{"class":4988},[470,15343,5194],{"class":4992},[470,15345,333],{"class":4988},[470,15347,9048],{"class":5025},[470,15349,6517],{"class":4992},[470,15351,5049],{"class":4988},[470,15353,15354,15357,15359,15361,15363,15365,15368,15370,15372,15374,15376],{"class":472,"line":5052},[470,15355,15356],{"class":5036},"  currentPeriodEnd",[470,15358,1149],{"class":4988},[470,15360,9137],{"class":5025},[470,15362,5028],{"class":4992},[470,15364,5006],{"class":499},[470,15366,15367],{"class":489},"current_period_end",[470,15369,5006],{"class":499},[470,15371,5194],{"class":4992},[470,15373,333],{"class":4988},[470,15375,9048],{"class":5025},[470,15377,6103],{"class":4992},[470,15379,15380,15382,15384],{"class":472,"line":5069},[470,15381,5191],{"class":4988},[470,15383,5194],{"class":4992},[470,15385,5009],{"class":4988},[4903,15387],{},[338,15389,15391],{"id":15390},"from-idea-to-product-in-days-not-months","From Idea to Product in Days, Not Months",[321,15393,15394],{},"By combining the speed of generative AI with a solid, enterprise-grade architectural base, you get the best of both worlds:",[424,15396,15397,15403,15409],{},[359,15398,15399,15402],{},[330,15400,15401],{},"10x more speed"," to create new features.",[359,15404,15405,15408],{},[330,15406,15407],{},"0 regressions"," thanks to strict type contracts.",[359,15410,15411,15414],{},[330,15412,15413],{},"Clean code you own forever",", ready to receive thousands of paying customers.",[321,15416,15417],{},"Stop fighting AI hallucinations. Start your project on a professional, tested foundation.",[321,15419,15420],{},[455,15421,15422],{"href":6318},[330,15423,15424],{},"Get your immediate access to the Nuxfire source code and join the VIP Group →",[1945,15426,15427],{},"html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}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 .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}",{"title":349,"searchDepth":484,"depth":484,"links":15429},[15430,15435],{"id":14951,"depth":484,"text":14952,"children":15431},[15432,15433,15434],{"id":14963,"depth":509,"text":14964},{"id":15148,"depth":509,"text":15149},{"id":15178,"depth":509,"text":15179},{"id":15390,"depth":484,"text":15391},"AI-Assisted Development","Why the secret to building products with AI is not the prompt but the architecture. See how TypeScript contracts and tRPC schemas eliminate LLM hallucinations.","\u002Fimages\u002Fblog\u002Fvibe-coding-ai-cursor-claude-en.svg",{},"\u002Fimages\u002Fblog\u002Fvibe-coding-ai-cursor-claude-en.png","\u002Fblog\u002Fvibe-coding-ai-cursor-claude-code-saas",{"title":14912,"description":15437},"blog\u002Fvibe-coding-ai-cursor-claude-code-saas",[14924,14927,14930,15445,8902],"Vibe Coding",{"en":15447,"pt-br":15448,"es":15449},"vibe-coding-ai-cursor-claude-code-saas","vibe-coding-ia-cursor-claude-code-saas","vibe-coding-con-ia-cursor-claude-code-saas","81OSaqgFmrA1ZXpEBOL6V3_XuzITvJCVvHbkJSN6xTY",{"id":15452,"title":15453,"author":3439,"authorType":316,"body":15454,"category":15949,"date":5401,"description":15950,"extension":1971,"image":15951,"lang":4871,"meta":15952,"navigation":22,"ogImage":15953,"path":15954,"readTime":5407,"seo":15955,"stem":15956,"tags":15957,"translations":15958,"updated":316,"__hash__":15959},"blog\u002Fblog\u002Fvibe-coding-con-ia-cursor-claude-code-saas.md","Vibe Coding con IA: Cómo el Tipado Estricto y las Nuxt Layers Potencian a Cursor y Claude Code",{"type":318,"value":15455,"toc":15941},[15456,15469,15475,15478,15484,15486,15490,15493,15498,15502,15513,15663,15670,15672,15676,15682,15695,15698,15700,15704,15710,15900,15902,15906,15909,15929,15932,15939],[321,15457,15458,15459,15461,15462,965,15464,3518,15466,15468],{},"El término ",[330,15460,14920],{}," se ha convertido en la mayor tendencia del desarrollo de software en 2026. Desarrolladores con experiencia y fundadores técnicos usan asistentes inteligentes como ",[330,15463,14924],{},[330,15465,14927],{},[330,15467,14930],{}," para escribir código a velocidades nunca antes imaginadas.",[321,15470,15471,15472,333],{},"Sin embargo, casi todos los que intentan crear un SaaS complejo solo con prompts se topan con el mismo obstáculo: ",[330,15473,15474],{},"la entropía del código",[321,15476,15477],{},"Tras algunas iteraciones, la IA empieza a alucinar variables que no existen, recrea funciones ya implementadas en otros archivos, rompe pantallas enteras e introduce vulnerabilidades silenciosas en la base de datos.",[321,15479,15480,15481,15483],{},"¿Por qué ocurre esto? ¿Y cómo lo resuelve la arquitectura de ",[330,15482,4900],{},"?",[4903,15485],{},[338,15487,15489],{"id":15488},"el-secreto-no-es-el-prompt-es-el-contexto-estructural","El Secreto no es el Prompt, es el Contexto Estructural",[321,15491,15492],{},"Los grandes modelos de lenguaje (LLM) son tan buenos como el contexto que reciben en su ventana de atención. Si tu proyecto es un monolito confuso con cientos de archivos interdependientes sin tipado estricto, el modelo tiene que \"adivinar\" las relaciones entre las partes.",[321,15494,15495,15497],{},[330,15496,4900],{}," fue diseñado específicamente para ser la base ideal para las herramientas de IA, combinando tres pilares arquitectónicos:",[6406,15499,15501],{"id":15500},"_1-tipado-estricto-de-extremo-a-extremo-con-typescript-y-trpc","1. Tipado Estricto de Extremo a Extremo con TypeScript y tRPC",[321,15503,15504,15505,15507,15508,15510,15511,1149],{},"En Nuxfire no existen objetos ",[325,15506,14970],{}," ni payloads sin tipar. Cada ruta de API define su input y su output con esquemas ",[330,15509,14974],{}," a través de ",[330,15512,14978],{},[343,15514,15516],{"className":4975,"code":15515,"language":4977,"meta":349,"style":349},"export const billingRouter = router({\n  createCheckoutSession: protectedProcedure\n    .input(z.object({\n      priceId: z.string(),\n      teamId: z.string()\n    }))\n    .mutation(async ({ input, ctx }) => {\n      \u002F\u002F Cursor sabe exactamente qué propiedades existen en 'input' y 'ctx'\n      return createStripeSession(input.teamId, input.priceId);\n    })\n});\n",[325,15517,15518,15534,15542,15558,15574,15588,15594,15618,15623,15649,15655],{"__ignoreMap":349},[470,15519,15520,15522,15524,15526,15528,15530,15532],{"class":472,"line":473},[470,15521,5019],{"class":4984},[470,15523,5223],{"class":5222},[470,15525,14992],{"class":5226},[470,15527,5229],{"class":663},[470,15529,9246],{"class":5025},[470,15531,5028],{"class":4992},[470,15533,5031],{"class":4988},[470,15535,15536,15538,15540],{"class":472,"line":484},[470,15537,15005],{"class":5036},[470,15539,1149],{"class":4988},[470,15541,9260],{"class":4992},[470,15543,15544,15546,15548,15550,15552,15554,15556],{"class":472,"line":509},[470,15545,9265],{"class":4988},[470,15547,1863],{"class":5025},[470,15549,9270],{"class":4992},[470,15551,333],{"class":4988},[470,15553,9275],{"class":5025},[470,15555,5028],{"class":4992},[470,15557,5031],{"class":4988},[470,15559,15560,15562,15564,15566,15568,15570,15572],{"class":472,"line":528},[470,15561,15030],{"class":5036},[470,15563,1149],{"class":4988},[470,15565,9288],{"class":4992},[470,15567,333],{"class":4988},[470,15569,9293],{"class":5025},[470,15571,6517],{"class":4992},[470,15573,5049],{"class":4988},[470,15575,15576,15578,15580,15582,15584,15586],{"class":472,"line":5052},[470,15577,15047],{"class":5036},[470,15579,1149],{"class":4988},[470,15581,9288],{"class":4992},[470,15583,333],{"class":4988},[470,15585,9293],{"class":5025},[470,15587,6103],{"class":4992},[470,15589,15590,15592],{"class":472,"line":5069},[470,15591,9466],{"class":4988},[470,15593,9301],{"class":4992},[470,15595,15596,15598,15600,15602,15604,15606,15608,15610,15612,15614,15616],{"class":472,"line":5092},[470,15597,9265],{"class":4988},[470,15599,15070],{"class":5025},[470,15601,5028],{"class":4992},[470,15603,5956],{"class":5955},[470,15605,9314],{"class":4988},[470,15607,9322],{"class":5961},[470,15609,5109],{"class":4988},[470,15611,9317],{"class":5961},[470,15613,9325],{"class":4988},[470,15615,5967],{"class":5222},[470,15617,5131],{"class":4988},[470,15619,15620],{"class":472,"line":5123},[470,15621,15622],{"class":5326},"      \u002F\u002F Cursor sabe exactamente qué propiedades existen en 'input' y 'ctx'\n",[470,15624,15625,15627,15629,15631,15633,15635,15637,15639,15641,15643,15645,15647],{"class":472,"line":5134},[470,15626,9453],{"class":4984},[470,15628,15100],{"class":5025},[470,15630,5028],{"class":5036},[470,15632,1863],{"class":4992},[470,15634,333],{"class":4988},[470,15636,10797],{"class":4992},[470,15638,5109],{"class":4988},[470,15640,9322],{"class":4992},[470,15642,333],{"class":4988},[470,15644,15117],{"class":4992},[470,15646,5194],{"class":5036},[470,15648,5009],{"class":4988},[470,15650,15651,15653],{"class":472,"line":5151},[470,15652,9466],{"class":4988},[470,15654,6271],{"class":4992},[470,15656,15657,15659,15661],{"class":472,"line":5168},[470,15658,5191],{"class":4988},[470,15660,5194],{"class":4992},[470,15662,5009],{"class":4988},[321,15664,15665,15666,15669],{},"Cuando le pides a Cursor o a Claude Code: ",[15140,15667,15668],{},"\"Crea un botón en el panel que llame a la procedure de checkout\"",", el modelo lee las definiciones de tipos de tRPC y autocompleta los parámetros correctos al primer intento, sin inventar propiedades falsas.",[4903,15671],{},[6406,15673,15675],{"id":15674},"_2-nuxt-layers-límites-claros-de-dominio","2. Nuxt Layers: Límites Claros de Dominio",[321,15677,15678,15679,15681],{},"Con las ",[330,15680,4896],{},", cada área funcional del producto queda confinada a su directorio correspondiente:",[424,15683,15684,15689],{},[359,15685,15686,15687,333],{},"¿Cambiar el flujo de miembros del equipo? Solo se cargan los archivos de ",[325,15688,15162],{},[359,15690,15691,15692,15694],{},"¿Modificar los estilos del tema? El archivo ",[325,15693,15169],{}," lo centraliza todo.",[321,15696,15697],{},"Esta compartimentación reduce el tamaño del contexto necesario e impide que la IA modifique por error archivos no relacionados.",[4903,15699],{},[6406,15701,15703],{"id":15702},"_3-schemas-estructurados-con-drizzle-orm","3. Schemas Estructurados con Drizzle ORM",[321,15705,15706,15707,15709],{},"Mapear relaciones de base de datos con ORMs heredados suele generar alucinaciones en las migraciones SQL. ",[330,15708,6748],{}," usa TypeScript declarativo puro, lo que facilita que la IA entienda con total precisión las foreign keys, los índices y las restricciones de integridad:",[343,15711,15712],{"className":4975,"code":15188,"language":4977,"meta":349,"style":349},[325,15713,15714,15738,15764,15806,15868,15892],{"__ignoreMap":349},[470,15715,15716,15718,15720,15722,15724,15726,15728,15730,15732,15734,15736],{"class":472,"line":473},[470,15717,5019],{"class":4984},[470,15719,5223],{"class":5222},[470,15721,15199],{"class":5226},[470,15723,5229],{"class":663},[470,15725,8981],{"class":5025},[470,15727,5028],{"class":4992},[470,15729,5006],{"class":499},[470,15731,15210],{"class":489},[470,15733,5006],{"class":499},[470,15735,5109],{"class":4988},[470,15737,5131],{"class":4988},[470,15739,15740,15742,15744,15746,15748,15750,15752,15754,15756,15758,15760,15762],{"class":472,"line":484},[470,15741,8999],{"class":5036},[470,15743,1149],{"class":4988},[470,15745,9004],{"class":5025},[470,15747,5028],{"class":4992},[470,15749,5006],{"class":499},[470,15751,6563],{"class":489},[470,15753,5006],{"class":499},[470,15755,5194],{"class":4992},[470,15757,333],{"class":4988},[470,15759,9019],{"class":5025},[470,15761,6517],{"class":4992},[470,15763,5049],{"class":4988},[470,15765,15766,15768,15770,15772,15774,15776,15778,15780,15782,15784,15786,15788,15790,15792,15794,15796,15798,15800,15802,15804],{"class":472,"line":509},[470,15767,10437],{"class":5036},[470,15769,1149],{"class":4988},[470,15771,9004],{"class":5025},[470,15773,5028],{"class":4992},[470,15775,5006],{"class":499},[470,15777,10184],{"class":489},[470,15779,5006],{"class":499},[470,15781,5194],{"class":4992},[470,15783,333],{"class":4988},[470,15785,9048],{"class":5025},[470,15787,6517],{"class":4992},[470,15789,333],{"class":4988},[470,15791,10462],{"class":5025},[470,15793,5028],{"class":4992},[470,15795,6517],{"class":4988},[470,15797,5967],{"class":5222},[470,15799,10207],{"class":4992},[470,15801,333],{"class":4988},[470,15803,15283],{"class":4992},[470,15805,5049],{"class":4988},[470,15807,15808,15810,15812,15814,15816,15818,15820,15822,15824,15826,15828,15830,15832,15834,15836,15838,15840,15842,15844,15846,15848,15850,15852,15854,15856,15858,15860,15862,15864,15866],{"class":472,"line":528},[470,15809,15290],{"class":5036},[470,15811,1149],{"class":4988},[470,15813,9004],{"class":5025},[470,15815,5028],{"class":4992},[470,15817,5006],{"class":499},[470,15819,14059],{"class":489},[470,15821,5006],{"class":499},[470,15823,5109],{"class":4988},[470,15825,4989],{"class":4988},[470,15827,9084],{"class":5036},[470,15829,1149],{"class":4988},[470,15831,5077],{"class":4992},[470,15833,5006],{"class":499},[470,15835,15317],{"class":489},[470,15837,5006],{"class":499},[470,15839,5109],{"class":4988},[470,15841,500],{"class":499},[470,15843,15326],{"class":489},[470,15845,5006],{"class":499},[470,15847,5109],{"class":4988},[470,15849,500],{"class":499},[470,15851,15335],{"class":489},[470,15853,5006],{"class":499},[470,15855,9115],{"class":4992},[470,15857,5191],{"class":4988},[470,15859,5194],{"class":4992},[470,15861,333],{"class":4988},[470,15863,9048],{"class":5025},[470,15865,6517],{"class":4992},[470,15867,5049],{"class":4988},[470,15869,15870,15872,15874,15876,15878,15880,15882,15884,15886,15888,15890],{"class":472,"line":5052},[470,15871,15356],{"class":5036},[470,15873,1149],{"class":4988},[470,15875,9137],{"class":5025},[470,15877,5028],{"class":4992},[470,15879,5006],{"class":499},[470,15881,15367],{"class":489},[470,15883,5006],{"class":499},[470,15885,5194],{"class":4992},[470,15887,333],{"class":4988},[470,15889,9048],{"class":5025},[470,15891,6103],{"class":4992},[470,15893,15894,15896,15898],{"class":472,"line":5069},[470,15895,5191],{"class":4988},[470,15897,5194],{"class":4992},[470,15899,5009],{"class":4988},[4903,15901],{},[338,15903,15905],{"id":15904},"de-la-idea-al-producto-en-días-no-en-meses","De la Idea al Producto en Días, no en Meses",[321,15907,15908],{},"Al combinar la velocidad de la IA generativa con una base arquitectónica sólida de nivel enterprise, obtienes lo mejor de ambos mundos:",[424,15910,15911,15917,15923],{},[359,15912,15913,15916],{},[330,15914,15915],{},"10x más velocidad"," para crear nuevas funcionalidades.",[359,15918,15919,15922],{},[330,15920,15921],{},"0 regresiones"," gracias a los contratos estrictos de tipos.",[359,15924,15925,15928],{},[330,15926,15927],{},"Código limpio que es tuyo para siempre",", listo para recibir a miles de clientes que pagan.",[321,15930,15931],{},"Basta de luchar contra las alucinaciones de la IA. Empieza tu proyecto sobre una base profesional y probada.",[321,15933,15934],{},[455,15935,15936],{"href":5385},[330,15937,15938],{},"Asegura tu acceso inmediato al código fuente de Nuxfire y entra al Grupo VIP →",[1945,15940,15427],{},{"title":349,"searchDepth":484,"depth":484,"links":15942},[15943,15948],{"id":15488,"depth":484,"text":15489,"children":15944},[15945,15946,15947],{"id":15500,"depth":509,"text":15501},{"id":15674,"depth":509,"text":15675},{"id":15702,"depth":509,"text":15703},{"id":15904,"depth":484,"text":15905},"Desarrollo con IA","Por qué el secreto de construir productos con IA no es el prompt, sino la arquitectura. Mira cómo los contratos de TypeScript y los schemas de tRPC eliminan las alucinaciones de los LLM.","\u002Fimages\u002Fblog\u002Fvibe-coding-ai-cursor-claude-es.svg",{},"\u002Fimages\u002Fblog\u002Fvibe-coding-ai-cursor-claude-es.png","\u002Fblog\u002Fvibe-coding-con-ia-cursor-claude-code-saas",{"title":15453,"description":15950},"blog\u002Fvibe-coding-con-ia-cursor-claude-code-saas",[14924,14927,14930,15445,8902],{"en":15447,"pt-br":15448,"es":15449},"85KPVNw8E00kgEHVbLbLWex8HgsgOsFClIaxbJSloH4",{"id":15961,"title":15962,"author":315,"authorType":316,"body":15963,"category":16459,"date":5401,"description":16460,"extension":1971,"image":16461,"lang":1973,"meta":16462,"navigation":22,"ogImage":16463,"path":16464,"readTime":5407,"seo":16465,"stem":16466,"tags":16467,"translations":16468,"updated":316,"__hash__":16469},"blog\u002Fblog\u002Fvibe-coding-ia-cursor-claude-code-saas.md","Vibe Coding com IA: Como Tipagem Estrita e Nuxt Layers Potencializam o Cursor e Claude Code",{"type":318,"value":15964,"toc":16451},[15965,15978,15984,15987,15993,15995,15999,16002,16007,16011,16022,16172,16179,16181,16185,16191,16205,16208,16210,16214,16220,16410,16412,16416,16419,16439,16442,16449],[321,15966,15967,15968,15970,15971,965,15973,415,15975,15977],{},"O termo ",[330,15969,14920],{}," tornou-se a maior tendência de desenvolvimento de software em 2026. Desenvolvedores experientes e fundadores técnicos usam assistentes inteligentes como ",[330,15972,14924],{},[330,15974,14927],{},[330,15976,14930],{}," para escrever código em velocidades nunca antes imaginadas.",[321,15979,15980,15981,333],{},"No entanto, quase todo mundo que tenta criar um SaaS complexo apenas com prompts esbarra no mesmo obstáculo: ",[330,15982,15983],{},"a entropia do código",[321,15985,15986],{},"Após algumas iterações, a IA começa a alucinar variáveis que não existem, recria funções já implementadas em outros arquivos, quebra telas inteiras e introduz vulnerabilidades silenciosas de banco de dados.",[321,15988,15989,15990,15992],{},"Por que isso acontece? E como a arquitetura do ",[330,15991,4900],{}," resolve esse problema?",[4903,15994],{},[338,15996,15998],{"id":15997},"o-segredo-não-é-o-prompt-é-o-contexto-estrutural","O Segredo não é o Prompt, é o Contexto Estrutural",[321,16000,16001],{},"Grandes modelos de linguagem (LLMs) são tão bons quanto o contexto que recebem na janela de atenção. Se o seu projeto é um monólito confuso com centenas de arquivos interdependentes sem tipagem estrita, o modelo precisa \"adivinhar\" as relações entre as partes.",[321,16003,680,16004,16006],{},[330,16005,4900],{}," foi projetado especificamente para ser a fundação ideal para ferramentas de IA, combinando três pilares arquiteturais:",[6406,16008,16010],{"id":16009},"_1-tipagem-estrita-de-ponta-a-ponta-com-typescript-trpc","1. Tipagem Estrita de Ponta a Ponta com TypeScript & tRPC",[321,16012,16013,16014,16016,16017,16019,16020,1149],{},"No Nuxfire, não existem objetos ",[325,16015,14970],{}," ou payloads não tipados. Cada rota de API define seu input e output com esquemas ",[330,16018,14974],{}," através do ",[330,16021,14978],{},[343,16023,16025],{"className":4975,"code":16024,"language":4977,"meta":349,"style":349},"export const billingRouter = router({\n  createCheckoutSession: protectedProcedure\n    .input(z.object({\n      priceId: z.string(),\n      teamId: z.string()\n    }))\n    .mutation(async ({ input, ctx }) => {\n      \u002F\u002F O Cursor sabe exatamente quais propriedades existem em 'input' e 'ctx'\n      return createStripeSession(input.teamId, input.priceId);\n    })\n});\n",[325,16026,16027,16043,16051,16067,16083,16097,16103,16127,16132,16158,16164],{"__ignoreMap":349},[470,16028,16029,16031,16033,16035,16037,16039,16041],{"class":472,"line":473},[470,16030,5019],{"class":4984},[470,16032,5223],{"class":5222},[470,16034,14992],{"class":5226},[470,16036,5229],{"class":663},[470,16038,9246],{"class":5025},[470,16040,5028],{"class":4992},[470,16042,5031],{"class":4988},[470,16044,16045,16047,16049],{"class":472,"line":484},[470,16046,15005],{"class":5036},[470,16048,1149],{"class":4988},[470,16050,9260],{"class":4992},[470,16052,16053,16055,16057,16059,16061,16063,16065],{"class":472,"line":509},[470,16054,9265],{"class":4988},[470,16056,1863],{"class":5025},[470,16058,9270],{"class":4992},[470,16060,333],{"class":4988},[470,16062,9275],{"class":5025},[470,16064,5028],{"class":4992},[470,16066,5031],{"class":4988},[470,16068,16069,16071,16073,16075,16077,16079,16081],{"class":472,"line":528},[470,16070,15030],{"class":5036},[470,16072,1149],{"class":4988},[470,16074,9288],{"class":4992},[470,16076,333],{"class":4988},[470,16078,9293],{"class":5025},[470,16080,6517],{"class":4992},[470,16082,5049],{"class":4988},[470,16084,16085,16087,16089,16091,16093,16095],{"class":472,"line":5052},[470,16086,15047],{"class":5036},[470,16088,1149],{"class":4988},[470,16090,9288],{"class":4992},[470,16092,333],{"class":4988},[470,16094,9293],{"class":5025},[470,16096,6103],{"class":4992},[470,16098,16099,16101],{"class":472,"line":5069},[470,16100,9466],{"class":4988},[470,16102,9301],{"class":4992},[470,16104,16105,16107,16109,16111,16113,16115,16117,16119,16121,16123,16125],{"class":472,"line":5092},[470,16106,9265],{"class":4988},[470,16108,15070],{"class":5025},[470,16110,5028],{"class":4992},[470,16112,5956],{"class":5955},[470,16114,9314],{"class":4988},[470,16116,9322],{"class":5961},[470,16118,5109],{"class":4988},[470,16120,9317],{"class":5961},[470,16122,9325],{"class":4988},[470,16124,5967],{"class":5222},[470,16126,5131],{"class":4988},[470,16128,16129],{"class":472,"line":5123},[470,16130,16131],{"class":5326},"      \u002F\u002F O Cursor sabe exatamente quais propriedades existem em 'input' e 'ctx'\n",[470,16133,16134,16136,16138,16140,16142,16144,16146,16148,16150,16152,16154,16156],{"class":472,"line":5134},[470,16135,9453],{"class":4984},[470,16137,15100],{"class":5025},[470,16139,5028],{"class":5036},[470,16141,1863],{"class":4992},[470,16143,333],{"class":4988},[470,16145,10797],{"class":4992},[470,16147,5109],{"class":4988},[470,16149,9322],{"class":4992},[470,16151,333],{"class":4988},[470,16153,15117],{"class":4992},[470,16155,5194],{"class":5036},[470,16157,5009],{"class":4988},[470,16159,16160,16162],{"class":472,"line":5151},[470,16161,9466],{"class":4988},[470,16163,6271],{"class":4992},[470,16165,16166,16168,16170],{"class":472,"line":5168},[470,16167,5191],{"class":4988},[470,16169,5194],{"class":4992},[470,16171,5009],{"class":4988},[321,16173,16174,16175,16178],{},"Quando você pede para o Cursor ou Claude Code: ",[15140,16176,16177],{},"\"Crie um botão no painel que chama a procedure de checkout\"",", o modelo lê as definições de tipo do tRPC e autocompleta os parâmetros corretos na primeira tentativa, sem inventar propriedades falsas.",[4903,16180],{},[6406,16182,16184],{"id":16183},"_2-nuxt-layers-limites-claros-de-domínio","2. Nuxt Layers: Limites Claros de Domínio",[321,16186,16187,16188,16190],{},"Com as ",[330,16189,4896],{},", cada área funcional do produto fica confinada ao seu diretório correspondente:",[424,16192,16193,16199],{},[359,16194,16195,16196,16198],{},"Alterar o fluxo de membros da equipe? Apenas arquivos em ",[325,16197,15162],{}," são carregados.",[359,16200,16201,16202,16204],{},"Modificar estilos do tema? O arquivo ",[325,16203,15169],{}," centraliza tudo.",[321,16206,16207],{},"Essa compartimentação reduz o tamanho do contexto necessário e impede que a IA altere arquivos não relacionados por engano.",[4903,16209],{},[6406,16211,16213],{"id":16212},"_3-schemas-estruturados-com-drizzle-orm","3. Schemas Estruturados com Drizzle ORM",[321,16215,16216,16217,16219],{},"Mapear relacionamentos de banco com ORMs legados costuma gerar alucinações em migrações SQL. O ",[330,16218,6748],{}," utiliza puro TypeScript declarativo, facilitando para a IA entender foreign keys, índices e restrições de integridade com total precisão:",[343,16221,16222],{"className":4975,"code":15188,"language":4977,"meta":349,"style":349},[325,16223,16224,16248,16274,16316,16378,16402],{"__ignoreMap":349},[470,16225,16226,16228,16230,16232,16234,16236,16238,16240,16242,16244,16246],{"class":472,"line":473},[470,16227,5019],{"class":4984},[470,16229,5223],{"class":5222},[470,16231,15199],{"class":5226},[470,16233,5229],{"class":663},[470,16235,8981],{"class":5025},[470,16237,5028],{"class":4992},[470,16239,5006],{"class":499},[470,16241,15210],{"class":489},[470,16243,5006],{"class":499},[470,16245,5109],{"class":4988},[470,16247,5131],{"class":4988},[470,16249,16250,16252,16254,16256,16258,16260,16262,16264,16266,16268,16270,16272],{"class":472,"line":484},[470,16251,8999],{"class":5036},[470,16253,1149],{"class":4988},[470,16255,9004],{"class":5025},[470,16257,5028],{"class":4992},[470,16259,5006],{"class":499},[470,16261,6563],{"class":489},[470,16263,5006],{"class":499},[470,16265,5194],{"class":4992},[470,16267,333],{"class":4988},[470,16269,9019],{"class":5025},[470,16271,6517],{"class":4992},[470,16273,5049],{"class":4988},[470,16275,16276,16278,16280,16282,16284,16286,16288,16290,16292,16294,16296,16298,16300,16302,16304,16306,16308,16310,16312,16314],{"class":472,"line":509},[470,16277,10437],{"class":5036},[470,16279,1149],{"class":4988},[470,16281,9004],{"class":5025},[470,16283,5028],{"class":4992},[470,16285,5006],{"class":499},[470,16287,10184],{"class":489},[470,16289,5006],{"class":499},[470,16291,5194],{"class":4992},[470,16293,333],{"class":4988},[470,16295,9048],{"class":5025},[470,16297,6517],{"class":4992},[470,16299,333],{"class":4988},[470,16301,10462],{"class":5025},[470,16303,5028],{"class":4992},[470,16305,6517],{"class":4988},[470,16307,5967],{"class":5222},[470,16309,10207],{"class":4992},[470,16311,333],{"class":4988},[470,16313,15283],{"class":4992},[470,16315,5049],{"class":4988},[470,16317,16318,16320,16322,16324,16326,16328,16330,16332,16334,16336,16338,16340,16342,16344,16346,16348,16350,16352,16354,16356,16358,16360,16362,16364,16366,16368,16370,16372,16374,16376],{"class":472,"line":528},[470,16319,15290],{"class":5036},[470,16321,1149],{"class":4988},[470,16323,9004],{"class":5025},[470,16325,5028],{"class":4992},[470,16327,5006],{"class":499},[470,16329,14059],{"class":489},[470,16331,5006],{"class":499},[470,16333,5109],{"class":4988},[470,16335,4989],{"class":4988},[470,16337,9084],{"class":5036},[470,16339,1149],{"class":4988},[470,16341,5077],{"class":4992},[470,16343,5006],{"class":499},[470,16345,15317],{"class":489},[470,16347,5006],{"class":499},[470,16349,5109],{"class":4988},[470,16351,500],{"class":499},[470,16353,15326],{"class":489},[470,16355,5006],{"class":499},[470,16357,5109],{"class":4988},[470,16359,500],{"class":499},[470,16361,15335],{"class":489},[470,16363,5006],{"class":499},[470,16365,9115],{"class":4992},[470,16367,5191],{"class":4988},[470,16369,5194],{"class":4992},[470,16371,333],{"class":4988},[470,16373,9048],{"class":5025},[470,16375,6517],{"class":4992},[470,16377,5049],{"class":4988},[470,16379,16380,16382,16384,16386,16388,16390,16392,16394,16396,16398,16400],{"class":472,"line":5052},[470,16381,15356],{"class":5036},[470,16383,1149],{"class":4988},[470,16385,9137],{"class":5025},[470,16387,5028],{"class":4992},[470,16389,5006],{"class":499},[470,16391,15367],{"class":489},[470,16393,5006],{"class":499},[470,16395,5194],{"class":4992},[470,16397,333],{"class":4988},[470,16399,9048],{"class":5025},[470,16401,6103],{"class":4992},[470,16403,16404,16406,16408],{"class":472,"line":5069},[470,16405,5191],{"class":4988},[470,16407,5194],{"class":4992},[470,16409,5009],{"class":4988},[4903,16411],{},[338,16413,16415],{"id":16414},"da-ideia-ao-produto-em-dias-não-em-meses","Da Ideia ao Produto em Dias, não em Meses",[321,16417,16418],{},"Ao combinar a velocidade da IA gerativa com uma base arquitetural sólida de nível enterprise, você obtém o melhor dos dois mundos:",[424,16420,16421,16427,16433],{},[359,16422,16423,16426],{},[330,16424,16425],{},"10x mais velocidade"," para criar novas funcionalidades.",[359,16428,16429,16432],{},[330,16430,16431],{},"0 regressões"," graças aos contratos estritos de tipos.",[359,16434,16435,16438],{},[330,16436,16437],{},"Código limpo que você possui para sempre",", pronto para receber milhares de clientes pagantes.",[321,16440,16441],{},"Chega de lutar contra alucinações da IA. Comece seu projeto sobre uma base profissional e testada.",[321,16443,16444],{},[455,16445,16446],{"href":5844},[330,16447,16448],{},"Garanta seu acesso imediato ao código-fonte do Nuxfire e entre no Grupo VIP →",[1945,16450,15427],{},{"title":349,"searchDepth":484,"depth":484,"links":16452},[16453,16458],{"id":15997,"depth":484,"text":15998,"children":16454},[16455,16456,16457],{"id":16009,"depth":509,"text":16010},{"id":16183,"depth":509,"text":16184},{"id":16212,"depth":509,"text":16213},{"id":16414,"depth":484,"text":16415},"Desenvolvimento com IA","Por que o segredo de construir produtos com IA não é o prompt, mas a arquitetura. Veja como contratos TypeScript e schemas tRPC eliminam alucinações de LLMs.","\u002Fimages\u002Fblog\u002Fvibe-coding-ai-cursor-claude.svg",{},"\u002Fimages\u002Fblog\u002Fvibe-coding-ai-cursor-claude.png","\u002Fblog\u002Fvibe-coding-ia-cursor-claude-code-saas",{"title":15962,"description":16460},"blog\u002Fvibe-coding-ia-cursor-claude-code-saas",[14924,14927,14930,15445,8902],{"en":15447,"pt-br":15448,"es":15449},"8s05SBDS2JHtFx3RcBYNLVEP8s3npDQ1_gdW0WvkvNU",{"id":16471,"title":16472,"author":315,"authorType":316,"body":16473,"category":17016,"date":5401,"description":17017,"extension":1971,"image":17018,"lang":1973,"meta":17019,"navigation":22,"ogImage":17020,"path":17021,"readTime":14207,"seo":17022,"stem":17023,"tags":17024,"translations":17026,"updated":316,"__hash__":17027},"blog\u002Fblog\u002Fwebsockets-durable-objects-tempo-real.md","WebSockets em Tempo Real sem Redis: Usando Cloudflare Durable Objects no Nuxt 4",{"type":318,"value":16474,"toc":17010},[16475,16478,16481,16492,16500,16502,16506,16513,16520,16526,16528,16532,16535,16549,16731,16733,16737,16740,16987,16989,16993,16996,17001,17008],[321,16476,16477],{},"Construir funcionalidades em tempo real (como notificações instantâneas, cursores colaborativos ou dashboards ao vivo) sempre foi um dos maiores pesadelos em arquiteturas serverless.",[321,16479,16480],{},"Tradicionalmente, a receita para suportar WebSockets exigia:",[356,16482,16483,16486,16489],{},[359,16484,16485],{},"Um cluster de servidores Node.js dedicados sempre ligados.",[359,16487,16488],{},"Um cluster Redis com Pub\u002FSub para sincronizar mensagens entre instâncias.",[359,16490,16491],{},"Um balanceador de carga configurado para conexões persistentes (Sticky Sessions).",[321,16493,16494,16495,16497,16498,333],{},"O custo mensal dessa infraestrutura mínima raramente ficava abaixo de centenas de dólares. O ",[330,16496,4900],{}," resolve esse desafio utilizando ",[330,16499,13629],{},[4903,16501],{},[338,16503,16505],{"id":16504},"o-que-são-cloudflare-durable-objects","O que são Cloudflare Durable Objects?",[321,16507,16508,16509,16512],{},"Durable Objects fornecem computação serverless com ",[330,16510,16511],{},"estado coordenado",". Cada objeto roda como uma instância única na rede global da Cloudflare, garantindo que todas as conexões para o mesmo \"canal\" ou \"sala\" cheguem ao mesmo processo de memória.",[321,16514,16515,16516,16519],{},"Além disso, cada Durable Object possui seu próprio banco ",[330,16517,16518],{},"SQLite embarcado",", permitindo persistir o histórico de mensagens com latência abaixo de 1 milissegundo.",[343,16521,16524],{"className":16522,"code":16523,"language":348},[346],"[Usuário A (Navegador)] ──WebSockets──> ┌────────────────────────────────┐\n                                       │ Cloudflare Durable Object      │\n[Usuário B (Navegador)] ──WebSockets──> │ - Estado de conexões em RAM    │\n                                       │ - Banco SQLite Embarcado       │\n[Webhook \u002F Worker Backend] ──Fetch────> └────────────────────────────────┘\n",[325,16525,16523],{"__ignoreMap":349},[4903,16527],{},[338,16529,16531],{"id":16530},"segurança-robusta-handshake-com-tickets-assinados-por-hmac","Segurança Robusta: Handshake com Tickets Assinados por HMAC",[321,16533,16534],{},"Conexões WebSockets não transmitem headers HTTP normais após o handshake inicial. Para garantir segurança máxima contra conexões forjadas, o Nuxfire utiliza tickets assinados criptograficamente:",[356,16536,16537,16540,16543,16546],{},[359,16538,16539],{},"O cliente faz uma requisição HTTP autenticada solicitando um ticket de conexão.",[359,16541,16542],{},"O servidor gera um token com HMAC assinado usando a chave secreta da aplicação e com validade de 60 segundos.",[359,16544,16545],{},"O cliente abre a conexão WebSocket passando o ticket na query string.",[359,16547,16548],{},"O Durable Object valida a assinatura antes de aceitar o handshake.",[343,16550,16552],{"className":4975,"code":16551,"language":4977,"meta":349,"style":349},"\u002F\u002F Gerando o ticket assinado no endpoint de autenticação\nexport function generateWebSocketTicket(userId: string, workspaceId: string) {\n  const expiresAt = Date.now() + 60 * 1000;\n  const payload = `${userId}:${workspaceId}:${expiresAt}`;\n  const signature = createHmac(\"sha256\", env.WS_SECRET).update(payload).digest(\"hex\");\n  return `${payload}:${signature}`;\n}\n",[325,16553,16554,16559,16587,16613,16649,16703,16727],{"__ignoreMap":349},[470,16555,16556],{"class":472,"line":473},[470,16557,16558],{"class":5326},"\u002F\u002F Gerando o ticket assinado no endpoint de autenticação\n",[470,16560,16561,16563,16565,16567,16569,16571,16573,16575,16577,16579,16581,16583,16585],{"class":472,"line":484},[470,16562,5019],{"class":4984},[470,16564,6805],{"class":5222},[470,16566,13697],{"class":5025},[470,16568,5028],{"class":4988},[470,16570,9389],{"class":5961},[470,16572,1149],{"class":663},[470,16574,6819],{"class":6818},[470,16576,5109],{"class":4988},[470,16578,13710],{"class":5961},[470,16580,1149],{"class":663},[470,16582,6819],{"class":6818},[470,16584,5194],{"class":4988},[470,16586,5131],{"class":4988},[470,16588,16589,16591,16593,16595,16597,16599,16601,16603,16605,16607,16609,16611],{"class":472,"line":509},[470,16590,5974],{"class":5222},[470,16592,13725],{"class":5226},[470,16594,5229],{"class":663},[470,16596,6095],{"class":4992},[470,16598,333],{"class":4988},[470,16600,6100],{"class":5025},[470,16602,9296],{"class":5036},[470,16604,13738],{"class":663},[470,16606,13741],{"class":6851},[470,16608,13744],{"class":663},[470,16610,13747],{"class":6851},[470,16612,5009],{"class":4988},[470,16614,16615,16617,16619,16621,16623,16625,16627,16629,16631,16633,16635,16637,16639,16641,16643,16645,16647],{"class":472,"line":528},[470,16616,5974],{"class":5222},[470,16618,13756],{"class":5226},[470,16620,5229],{"class":663},[470,16622,13761],{"class":499},[470,16624,13765],{"class":13764},[470,16626,9389],{"class":4992},[470,16628,5191],{"class":13764},[470,16630,1149],{"class":489},[470,16632,13765],{"class":13764},[470,16634,13776],{"class":4992},[470,16636,5191],{"class":13764},[470,16638,1149],{"class":489},[470,16640,13765],{"class":13764},[470,16642,13785],{"class":4992},[470,16644,5191],{"class":13764},[470,16646,13790],{"class":499},[470,16648,5009],{"class":4988},[470,16650,16651,16653,16655,16657,16659,16661,16663,16665,16667,16669,16671,16673,16675,16677,16679,16681,16683,16685,16687,16689,16691,16693,16695,16697,16699,16701],{"class":472,"line":5052},[470,16652,5974],{"class":5222},[470,16654,13799],{"class":5226},[470,16656,5229],{"class":663},[470,16658,13804],{"class":5025},[470,16660,5028],{"class":5036},[470,16662,5006],{"class":499},[470,16664,13811],{"class":489},[470,16666,5006],{"class":499},[470,16668,5109],{"class":4988},[470,16670,6011],{"class":4992},[470,16672,333],{"class":4988},[470,16674,13822],{"class":5226},[470,16676,5194],{"class":5036},[470,16678,333],{"class":4988},[470,16680,13829],{"class":5025},[470,16682,5028],{"class":5036},[470,16684,13834],{"class":4992},[470,16686,5194],{"class":5036},[470,16688,333],{"class":4988},[470,16690,13841],{"class":5025},[470,16692,5028],{"class":5036},[470,16694,5006],{"class":499},[470,16696,13848],{"class":489},[470,16698,5006],{"class":499},[470,16700,5194],{"class":5036},[470,16702,5009],{"class":4988},[470,16704,16705,16707,16709,16711,16713,16715,16717,16719,16721,16723,16725],{"class":472,"line":5069},[470,16706,6123],{"class":4984},[470,16708,13761],{"class":499},[470,16710,13765],{"class":13764},[470,16712,13834],{"class":4992},[470,16714,5191],{"class":13764},[470,16716,1149],{"class":489},[470,16718,13765],{"class":13764},[470,16720,13873],{"class":4992},[470,16722,5191],{"class":13764},[470,16724,13790],{"class":499},[470,16726,5009],{"class":4988},[470,16728,16729],{"class":472,"line":5092},[470,16730,6887],{"class":4988},[4903,16732],{},[338,16734,16736],{"id":16735},"consumindo-no-frontend-com-nuxt-composables","Consumindo no Frontend com Nuxt Composables",[321,16738,16739],{},"No lado do cliente Nuxt 4, conectar-se ao canal da equipe é tão simples quanto usar um composable nativo:",[343,16741,16743],{"className":9481,"code":16742,"language":9483,"meta":349,"style":349},"\u003Cscript setup lang=\"ts\">\nconst { messages, status, send } = useTeamWebSockets();\n\nfunction handleNotify() {\n  send({ type: \"TEAM_PING\", content: \"Nova tarefa criada no quadro!\" });\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CUBadge :color=\"status === 'OPEN' ? 'success' : 'neutral'\">\n      {{ status === 'OPEN' ? 'Tempo Real Ativo' : 'Reconectando...' }}\n    \u003C\u002FUBadge>\n    \u003Cdiv v-for=\"msg in messages\" :key=\"msg.id\">\n      {{ msg.content }}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[325,16744,16745,16765,16791,16795,16805,16842,16846,16854,16858,16866,16874,16918,16923,16931,16959,16963,16971,16979],{"__ignoreMap":349},[470,16746,16747,16749,16751,16753,16755,16757,16759,16761,16763],{"class":472,"line":473},[470,16748,9495],{"class":4988},[470,16750,9499],{"class":9498},[470,16752,9503],{"class":9502},[470,16754,9506],{"class":9502},[470,16756,9509],{"class":4988},[470,16758,5006],{"class":499},[470,16760,9514],{"class":489},[470,16762,5006],{"class":499},[470,16764,9519],{"class":4988},[470,16766,16767,16769,16771,16773,16775,16777,16779,16781,16783,16785,16787,16789],{"class":472,"line":484},[470,16768,9524],{"class":5222},[470,16770,4989],{"class":4988},[470,16772,13926],{"class":5226},[470,16774,5109],{"class":4988},[470,16776,9560],{"class":5226},[470,16778,5109],{"class":4988},[470,16780,13935],{"class":5226},[470,16782,4996],{"class":4988},[470,16784,5229],{"class":663},[470,16786,13942],{"class":5025},[470,16788,6517],{"class":4992},[470,16790,5009],{"class":4988},[470,16792,16793],{"class":472,"line":509},[470,16794,5014],{"emptyLinePlaceholder":22},[470,16796,16797,16799,16801,16803],{"class":472,"line":528},[470,16798,13955],{"class":5222},[470,16800,13958],{"class":5025},[470,16802,6517],{"class":4988},[470,16804,5131],{"class":4988},[470,16806,16807,16809,16811,16813,16815,16817,16819,16821,16823,16825,16827,16829,16831,16834,16836,16838,16840],{"class":472,"line":5052},[470,16808,13967],{"class":5025},[470,16810,5028],{"class":5036},[470,16812,9280],{"class":4988},[470,16814,9182],{"class":5036},[470,16816,1149],{"class":4988},[470,16818,500],{"class":499},[470,16820,13980],{"class":489},[470,16822,5006],{"class":499},[470,16824,5109],{"class":4988},[470,16826,13987],{"class":5036},[470,16828,1149],{"class":4988},[470,16830,500],{"class":499},[470,16832,16833],{"class":489},"Nova tarefa criada no quadro!",[470,16835,5006],{"class":499},[470,16837,4996],{"class":4988},[470,16839,5194],{"class":5036},[470,16841,5009],{"class":4988},[470,16843,16844],{"class":472,"line":5069},[470,16845,6887],{"class":4988},[470,16847,16848,16850,16852],{"class":472,"line":5092},[470,16849,9624],{"class":4988},[470,16851,9499],{"class":9498},[470,16853,9519],{"class":4988},[470,16855,16856],{"class":472,"line":5123},[470,16857,5014],{"emptyLinePlaceholder":22},[470,16859,16860,16862,16864],{"class":472,"line":5134},[470,16861,9495],{"class":4988},[470,16863,14025],{"class":9498},[470,16865,9519],{"class":4988},[470,16867,16868,16870,16872],{"class":472,"line":5151},[470,16869,14032],{"class":4988},[470,16871,14035],{"class":9498},[470,16873,9519],{"class":4988},[470,16875,16876,16878,16880,16882,16884,16886,16888,16890,16892,16894,16896,16898,16900,16902,16904,16906,16908,16910,16912,16914,16916],{"class":472,"line":5168},[470,16877,14042],{"class":4988},[470,16879,14045],{"class":9498},[470,16881,14048],{"class":4988},[470,16883,14051],{"class":9502},[470,16885,9509],{"class":4988},[470,16887,5006],{"class":14056},[470,16889,14059],{"class":4992},[470,16891,14062],{"class":663},[470,16893,14065],{"class":499},[470,16895,14068],{"class":489},[470,16897,14071],{"class":499},[470,16899,14074],{"class":663},[470,16901,14065],{"class":499},[470,16903,14079],{"class":489},[470,16905,14071],{"class":499},[470,16907,14048],{"class":663},[470,16909,14065],{"class":499},[470,16911,14088],{"class":489},[470,16913,14071],{"class":499},[470,16915,5006],{"class":14056},[470,16917,9519],{"class":4988},[470,16919,16920],{"class":472,"line":5182},[470,16921,16922],{"class":4992},"      {{ status === 'OPEN' ? 'Tempo Real Ativo' : 'Reconectando...' }}\n",[470,16924,16925,16927,16929],{"class":472,"line":5188},[470,16926,14104],{"class":4988},[470,16928,14045],{"class":9498},[470,16930,9519],{"class":4988},[470,16932,16933,16935,16937,16939,16941,16943,16945,16947,16949,16951,16953,16955,16957],{"class":472,"line":6115},[470,16934,14042],{"class":4988},[470,16936,14035],{"class":9498},[470,16938,14117],{"class":9502},[470,16940,9509],{"class":4988},[470,16942,5006],{"class":499},[470,16944,14124],{"class":489},[470,16946,5006],{"class":499},[470,16948,14129],{"class":9502},[470,16950,9509],{"class":4988},[470,16952,5006],{"class":499},[470,16954,14136],{"class":489},[470,16956,5006],{"class":499},[470,16958,9519],{"class":4988},[470,16960,16961],{"class":472,"line":6120},[470,16962,14145],{"class":4992},[470,16964,16965,16967,16969],{"class":472,"line":6153},[470,16966,14104],{"class":4988},[470,16968,14035],{"class":9498},[470,16970,9519],{"class":4988},[470,16972,16973,16975,16977],{"class":472,"line":10926},[470,16974,14158],{"class":4988},[470,16976,14035],{"class":9498},[470,16978,9519],{"class":4988},[470,16980,16981,16983,16985],{"class":472,"line":10931},[470,16982,9624],{"class":4988},[470,16984,14025],{"class":9498},[470,16986,9519],{"class":4988},[4903,16988],{},[338,16990,16992],{"id":16991},"conclusão-tempo-real-de-nível-enterprise-ao-seu-alcance","Conclusão: Tempo Real de Nível Enterprise ao seu Alcance",[321,16994,16995],{},"Com os Durable Objects, você elimina completamente os custos de servidores Redis e Socket.io, aproveitando a rede global da Cloudflare para entregar uma experiência ultra-rápida aos seus usuários.",[321,16997,16998,16999,333],{},"Todo esse fluxo — desde os workers de backend até os composables reativos do Nuxt — já vem 100% implementado e testado no ",[330,17000,4900],{},[321,17002,17003],{},[455,17004,17005],{"href":5844},[330,17006,17007],{},"Garanta sua licença vitalícia e baixe o código agora mesmo →",[1945,17009,14194],{},{"title":349,"searchDepth":484,"depth":484,"links":17011},[17012,17013,17014,17015],{"id":16504,"depth":484,"text":16505},{"id":16530,"depth":484,"text":16531},{"id":16735,"depth":484,"text":16736},{"id":16991,"depth":484,"text":16992},"Tempo Real & WebSockets","Descubra como criar notificações instantâneas e colaboração multiusuário sem servidores Socket.io dedicados nem instâncias caras de Redis, usando Durable Objects.","\u002Fimages\u002Fblog\u002Fwebsockets-durable-objects.svg",{},"\u002Fimages\u002Fblog\u002Fwebsockets-durable-objects.png","\u002Fblog\u002Fwebsockets-durable-objects-tempo-real",{"title":16472,"description":17017},"blog\u002Fwebsockets-durable-objects-tempo-real",[78,14211,14212,17025],"Tempo Real",{"en":14215,"pt-br":14216,"es":14217},"i7LafJa5zYzXcuoT8dhVznqlIj_7k6-TjGi1mq_fcuc",{"id":17029,"title":17030,"author":3439,"authorType":316,"body":17031,"category":17574,"date":5401,"description":17575,"extension":1971,"image":17576,"lang":4871,"meta":17577,"navigation":22,"ogImage":17578,"path":17579,"readTime":14207,"seo":17580,"stem":17581,"tags":17582,"translations":17584,"updated":316,"__hash__":17585},"blog\u002Fblog\u002Fwebsockets-durable-objects-tiempo-real.md","WebSockets en Tiempo Real sin Redis: Usando Cloudflare Durable Objects en Nuxt 4",{"type":318,"value":17032,"toc":17568},[17033,17036,17039,17050,17058,17060,17064,17071,17078,17084,17086,17090,17093,17107,17289,17291,17295,17298,17545,17547,17551,17554,17559,17566],[321,17034,17035],{},"Construir funcionalidades en tiempo real (como notificaciones instantáneas, cursores colaborativos o dashboards en vivo) siempre ha sido una de las mayores pesadillas de las arquitecturas serverless.",[321,17037,17038],{},"Tradicionalmente, la receta para soportar WebSockets exigía:",[356,17040,17041,17044,17047],{},[359,17042,17043],{},"Un clúster de servidores Node.js dedicados siempre encendidos.",[359,17045,17046],{},"Un clúster de Redis con Pub\u002FSub para sincronizar mensajes entre instancias.",[359,17048,17049],{},"Un balanceador de carga configurado para conexiones persistentes (Sticky Sessions).",[321,17051,17052,17053,17055,17056,333],{},"El costo mensual de esta infraestructura mínima rara vez bajaba de cientos de dólares. ",[330,17054,4900],{}," resuelve este desafío utilizando ",[330,17057,13629],{},[4903,17059],{},[338,17061,17063],{"id":17062},"qué-son-los-cloudflare-durable-objects","¿Qué son los Cloudflare Durable Objects?",[321,17065,17066,17067,17070],{},"Los Durable Objects ofrecen cómputo serverless con ",[330,17068,17069],{},"estado coordinado",". Cada objeto se ejecuta como una instancia única en la red global de Cloudflare, lo que garantiza que todas las conexiones al mismo \"canal\" o \"sala\" lleguen al mismo proceso en memoria.",[321,17072,17073,17074,17077],{},"Además, cada Durable Object tiene su propia base de datos ",[330,17075,17076],{},"SQLite embebida",", que permite persistir el historial de mensajes con una latencia inferior a 1 milisegundo.",[343,17079,17082],{"className":17080,"code":17081,"language":348},[346],"[Usuario A (Navegador)] ──WebSockets──> ┌────────────────────────────────┐\n                                        │ Cloudflare Durable Object      │\n[Usuario B (Navegador)] ──WebSockets──> │ - Estado de conexiones en RAM  │\n                                        │ - Base SQLite Embebida         │\n[Webhook \u002F Worker Backend] ──Fetch────> └────────────────────────────────┘\n",[325,17083,17081],{"__ignoreMap":349},[4903,17085],{},[338,17087,17089],{"id":17088},"seguridad-robusta-handshake-con-tickets-firmados-con-hmac","Seguridad Robusta: Handshake con Tickets Firmados con HMAC",[321,17091,17092],{},"Las conexiones WebSocket no transmiten los headers HTTP normales después del handshake inicial. Para garantizar la máxima seguridad frente a conexiones falsificadas, Nuxfire utiliza tickets firmados criptográficamente:",[356,17094,17095,17098,17101,17104],{},[359,17096,17097],{},"El cliente hace una petición HTTP autenticada solicitando un ticket de conexión.",[359,17099,17100],{},"El servidor genera un token con HMAC firmado con la clave secreta de la aplicación y con una validez de 60 segundos.",[359,17102,17103],{},"El cliente abre la conexión WebSocket pasando el ticket en la query string.",[359,17105,17106],{},"El Durable Object valida la firma antes de aceptar el handshake.",[343,17108,17110],{"className":4975,"code":17109,"language":4977,"meta":349,"style":349},"\u002F\u002F Generando el ticket firmado en el endpoint de autenticación\nexport function generateWebSocketTicket(userId: string, workspaceId: string) {\n  const expiresAt = Date.now() + 60 * 1000;\n  const payload = `${userId}:${workspaceId}:${expiresAt}`;\n  const signature = createHmac(\"sha256\", env.WS_SECRET).update(payload).digest(\"hex\");\n  return `${payload}:${signature}`;\n}\n",[325,17111,17112,17117,17145,17171,17207,17261,17285],{"__ignoreMap":349},[470,17113,17114],{"class":472,"line":473},[470,17115,17116],{"class":5326},"\u002F\u002F Generando el ticket firmado en el endpoint de autenticación\n",[470,17118,17119,17121,17123,17125,17127,17129,17131,17133,17135,17137,17139,17141,17143],{"class":472,"line":484},[470,17120,5019],{"class":4984},[470,17122,6805],{"class":5222},[470,17124,13697],{"class":5025},[470,17126,5028],{"class":4988},[470,17128,9389],{"class":5961},[470,17130,1149],{"class":663},[470,17132,6819],{"class":6818},[470,17134,5109],{"class":4988},[470,17136,13710],{"class":5961},[470,17138,1149],{"class":663},[470,17140,6819],{"class":6818},[470,17142,5194],{"class":4988},[470,17144,5131],{"class":4988},[470,17146,17147,17149,17151,17153,17155,17157,17159,17161,17163,17165,17167,17169],{"class":472,"line":509},[470,17148,5974],{"class":5222},[470,17150,13725],{"class":5226},[470,17152,5229],{"class":663},[470,17154,6095],{"class":4992},[470,17156,333],{"class":4988},[470,17158,6100],{"class":5025},[470,17160,9296],{"class":5036},[470,17162,13738],{"class":663},[470,17164,13741],{"class":6851},[470,17166,13744],{"class":663},[470,17168,13747],{"class":6851},[470,17170,5009],{"class":4988},[470,17172,17173,17175,17177,17179,17181,17183,17185,17187,17189,17191,17193,17195,17197,17199,17201,17203,17205],{"class":472,"line":528},[470,17174,5974],{"class":5222},[470,17176,13756],{"class":5226},[470,17178,5229],{"class":663},[470,17180,13761],{"class":499},[470,17182,13765],{"class":13764},[470,17184,9389],{"class":4992},[470,17186,5191],{"class":13764},[470,17188,1149],{"class":489},[470,17190,13765],{"class":13764},[470,17192,13776],{"class":4992},[470,17194,5191],{"class":13764},[470,17196,1149],{"class":489},[470,17198,13765],{"class":13764},[470,17200,13785],{"class":4992},[470,17202,5191],{"class":13764},[470,17204,13790],{"class":499},[470,17206,5009],{"class":4988},[470,17208,17209,17211,17213,17215,17217,17219,17221,17223,17225,17227,17229,17231,17233,17235,17237,17239,17241,17243,17245,17247,17249,17251,17253,17255,17257,17259],{"class":472,"line":5052},[470,17210,5974],{"class":5222},[470,17212,13799],{"class":5226},[470,17214,5229],{"class":663},[470,17216,13804],{"class":5025},[470,17218,5028],{"class":5036},[470,17220,5006],{"class":499},[470,17222,13811],{"class":489},[470,17224,5006],{"class":499},[470,17226,5109],{"class":4988},[470,17228,6011],{"class":4992},[470,17230,333],{"class":4988},[470,17232,13822],{"class":5226},[470,17234,5194],{"class":5036},[470,17236,333],{"class":4988},[470,17238,13829],{"class":5025},[470,17240,5028],{"class":5036},[470,17242,13834],{"class":4992},[470,17244,5194],{"class":5036},[470,17246,333],{"class":4988},[470,17248,13841],{"class":5025},[470,17250,5028],{"class":5036},[470,17252,5006],{"class":499},[470,17254,13848],{"class":489},[470,17256,5006],{"class":499},[470,17258,5194],{"class":5036},[470,17260,5009],{"class":4988},[470,17262,17263,17265,17267,17269,17271,17273,17275,17277,17279,17281,17283],{"class":472,"line":5069},[470,17264,6123],{"class":4984},[470,17266,13761],{"class":499},[470,17268,13765],{"class":13764},[470,17270,13834],{"class":4992},[470,17272,5191],{"class":13764},[470,17274,1149],{"class":489},[470,17276,13765],{"class":13764},[470,17278,13873],{"class":4992},[470,17280,5191],{"class":13764},[470,17282,13790],{"class":499},[470,17284,5009],{"class":4988},[470,17286,17287],{"class":472,"line":5092},[470,17288,6887],{"class":4988},[4903,17290],{},[338,17292,17294],{"id":17293},"consumiéndolo-en-el-frontend-con-composables-de-nuxt","Consumiéndolo en el Frontend con Composables de Nuxt",[321,17296,17297],{},"En el lado del cliente Nuxt 4, conectarse al canal del equipo es tan simple como usar un composable nativo:",[343,17299,17301],{"className":9481,"code":17300,"language":9483,"meta":349,"style":349},"\u003Cscript setup lang=\"ts\">\nconst { messages, status, send } = useTeamWebSockets();\n\nfunction handleNotify() {\n  send({ type: \"TEAM_PING\", content: \"¡Nueva tarea creada en el tablero!\" });\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003CUBadge :color=\"status === 'OPEN' ? 'success' : 'neutral'\">\n      {{ status === 'OPEN' ? 'Tiempo Real Activo' : 'Reconectando...' }}\n    \u003C\u002FUBadge>\n    \u003Cdiv v-for=\"msg in messages\" :key=\"msg.id\">\n      {{ msg.content }}\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[325,17302,17303,17323,17349,17353,17363,17400,17404,17412,17416,17424,17432,17476,17481,17489,17517,17521,17529,17537],{"__ignoreMap":349},[470,17304,17305,17307,17309,17311,17313,17315,17317,17319,17321],{"class":472,"line":473},[470,17306,9495],{"class":4988},[470,17308,9499],{"class":9498},[470,17310,9503],{"class":9502},[470,17312,9506],{"class":9502},[470,17314,9509],{"class":4988},[470,17316,5006],{"class":499},[470,17318,9514],{"class":489},[470,17320,5006],{"class":499},[470,17322,9519],{"class":4988},[470,17324,17325,17327,17329,17331,17333,17335,17337,17339,17341,17343,17345,17347],{"class":472,"line":484},[470,17326,9524],{"class":5222},[470,17328,4989],{"class":4988},[470,17330,13926],{"class":5226},[470,17332,5109],{"class":4988},[470,17334,9560],{"class":5226},[470,17336,5109],{"class":4988},[470,17338,13935],{"class":5226},[470,17340,4996],{"class":4988},[470,17342,5229],{"class":663},[470,17344,13942],{"class":5025},[470,17346,6517],{"class":4992},[470,17348,5009],{"class":4988},[470,17350,17351],{"class":472,"line":509},[470,17352,5014],{"emptyLinePlaceholder":22},[470,17354,17355,17357,17359,17361],{"class":472,"line":528},[470,17356,13955],{"class":5222},[470,17358,13958],{"class":5025},[470,17360,6517],{"class":4988},[470,17362,5131],{"class":4988},[470,17364,17365,17367,17369,17371,17373,17375,17377,17379,17381,17383,17385,17387,17389,17392,17394,17396,17398],{"class":472,"line":5052},[470,17366,13967],{"class":5025},[470,17368,5028],{"class":5036},[470,17370,9280],{"class":4988},[470,17372,9182],{"class":5036},[470,17374,1149],{"class":4988},[470,17376,500],{"class":499},[470,17378,13980],{"class":489},[470,17380,5006],{"class":499},[470,17382,5109],{"class":4988},[470,17384,13987],{"class":5036},[470,17386,1149],{"class":4988},[470,17388,500],{"class":499},[470,17390,17391],{"class":489},"¡Nueva tarea creada en el tablero!",[470,17393,5006],{"class":499},[470,17395,4996],{"class":4988},[470,17397,5194],{"class":5036},[470,17399,5009],{"class":4988},[470,17401,17402],{"class":472,"line":5069},[470,17403,6887],{"class":4988},[470,17405,17406,17408,17410],{"class":472,"line":5092},[470,17407,9624],{"class":4988},[470,17409,9499],{"class":9498},[470,17411,9519],{"class":4988},[470,17413,17414],{"class":472,"line":5123},[470,17415,5014],{"emptyLinePlaceholder":22},[470,17417,17418,17420,17422],{"class":472,"line":5134},[470,17419,9495],{"class":4988},[470,17421,14025],{"class":9498},[470,17423,9519],{"class":4988},[470,17425,17426,17428,17430],{"class":472,"line":5151},[470,17427,14032],{"class":4988},[470,17429,14035],{"class":9498},[470,17431,9519],{"class":4988},[470,17433,17434,17436,17438,17440,17442,17444,17446,17448,17450,17452,17454,17456,17458,17460,17462,17464,17466,17468,17470,17472,17474],{"class":472,"line":5168},[470,17435,14042],{"class":4988},[470,17437,14045],{"class":9498},[470,17439,14048],{"class":4988},[470,17441,14051],{"class":9502},[470,17443,9509],{"class":4988},[470,17445,5006],{"class":14056},[470,17447,14059],{"class":4992},[470,17449,14062],{"class":663},[470,17451,14065],{"class":499},[470,17453,14068],{"class":489},[470,17455,14071],{"class":499},[470,17457,14074],{"class":663},[470,17459,14065],{"class":499},[470,17461,14079],{"class":489},[470,17463,14071],{"class":499},[470,17465,14048],{"class":663},[470,17467,14065],{"class":499},[470,17469,14088],{"class":489},[470,17471,14071],{"class":499},[470,17473,5006],{"class":14056},[470,17475,9519],{"class":4988},[470,17477,17478],{"class":472,"line":5182},[470,17479,17480],{"class":4992},"      {{ status === 'OPEN' ? 'Tiempo Real Activo' : 'Reconectando...' }}\n",[470,17482,17483,17485,17487],{"class":472,"line":5188},[470,17484,14104],{"class":4988},[470,17486,14045],{"class":9498},[470,17488,9519],{"class":4988},[470,17490,17491,17493,17495,17497,17499,17501,17503,17505,17507,17509,17511,17513,17515],{"class":472,"line":6115},[470,17492,14042],{"class":4988},[470,17494,14035],{"class":9498},[470,17496,14117],{"class":9502},[470,17498,9509],{"class":4988},[470,17500,5006],{"class":499},[470,17502,14124],{"class":489},[470,17504,5006],{"class":499},[470,17506,14129],{"class":9502},[470,17508,9509],{"class":4988},[470,17510,5006],{"class":499},[470,17512,14136],{"class":489},[470,17514,5006],{"class":499},[470,17516,9519],{"class":4988},[470,17518,17519],{"class":472,"line":6120},[470,17520,14145],{"class":4992},[470,17522,17523,17525,17527],{"class":472,"line":6153},[470,17524,14104],{"class":4988},[470,17526,14035],{"class":9498},[470,17528,9519],{"class":4988},[470,17530,17531,17533,17535],{"class":472,"line":10926},[470,17532,14158],{"class":4988},[470,17534,14035],{"class":9498},[470,17536,9519],{"class":4988},[470,17538,17539,17541,17543],{"class":472,"line":10931},[470,17540,9624],{"class":4988},[470,17542,14025],{"class":9498},[470,17544,9519],{"class":4988},[4903,17546],{},[338,17548,17550],{"id":17549},"conclusión-tiempo-real-de-nivel-enterprise-a-tu-alcance","Conclusión: Tiempo Real de Nivel Enterprise a tu Alcance",[321,17552,17553],{},"Con los Durable Objects, eliminas por completo los costos de los servidores Redis y Socket.io y aprovechas la red global de Cloudflare para ofrecer una experiencia ultrarrápida a tus usuarios.",[321,17555,17556,17557,333],{},"Todo este flujo — desde los workers de backend hasta los composables reactivos de Nuxt — ya viene 100 % implementado y probado en ",[330,17558,4900],{},[321,17560,17561],{},[455,17562,17563],{"href":5385},[330,17564,17565],{},"Asegura tu licencia vitalicia y descarga el código ahora mismo →",[1945,17567,14194],{},{"title":349,"searchDepth":484,"depth":484,"links":17569},[17570,17571,17572,17573],{"id":17062,"depth":484,"text":17063},{"id":17088,"depth":484,"text":17089},{"id":17293,"depth":484,"text":17294},{"id":17549,"depth":484,"text":17550},"Tiempo Real y WebSockets","Descubre cómo crear notificaciones instantáneas y colaboración multiusuario sin servidores Socket.io dedicados ni instancias caras de Redis, usando Durable Objects.","\u002Fimages\u002Fblog\u002Fwebsockets-durable-objects-es.svg",{},"\u002Fimages\u002Fblog\u002Fwebsockets-durable-objects-es.png","\u002Fblog\u002Fwebsockets-durable-objects-tiempo-real",{"title":17030,"description":17575},"blog\u002Fwebsockets-durable-objects-tiempo-real",[78,14211,14212,17583],"Tiempo Real",{"en":14215,"pt-br":14216,"es":14217},"mTjMghrO6fo33e0shAUDyA8ZX0YpLJIHddHhoRXPs7g",1790707497998]