[{"data":1,"prerenderedAt":1153},["ShallowReactive",2],{"navigation":3,"\u002Fblog\u002Fsubdomain-based-multi-tenancy-in-nuxt":142,"\u002Fblog\u002Fsubdomain-based-multi-tenancy-in-nuxt-surround":1149},[4],{"title":5,"path":6,"stem":7,"children":8,"page":141},"Blog","\u002Fblog","blog",[9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137],{"title":10,"path":11,"stem":12},"VueJS Introducing Dynamic Layouts using the Atomic Design Principles","\u002Fblog\u002Fvuejs-introducing-dynamic-layouts-using-the-atomic-design-principles","blog\u002F001.vuejs-introducing-dynamic-layouts-using-the-atomic-design-principles",{"title":14,"path":15,"stem":16},"Understanding JavaScript and Its Quirks","\u002Fblog\u002Funderstanding-javascript-and-its-quirks","blog\u002F002.understanding-javascript-and-its-quirks",{"title":18,"path":19,"stem":20},"Introducing Vue’s latest experimental Vapor Mode","\u002Fblog\u002Fintroducing-vues-latest-experimental-vapor-mode","blog\u002F003.introducing-vues-latest-experimental-vapor-mode",{"title":22,"path":23,"stem":24},"ECMAScript 2024 Nears Finalization","\u002Fblog\u002Fecmascript-2024-nears-finalization","blog\u002F004.ecmascript-2024-nears-finalization",{"title":26,"path":27,"stem":28},"UI Libraries for VueJS: Vuetify, Tailwind, and PrimeVue","\u002Fblog\u002Fui-libraries-for-vuejs-vuetify-tailwind-and-primevue","blog\u002F005.ui-libraries-for-vuejs-vuetify-tailwind-and-primevue",{"title":30,"path":31,"stem":32},"Micro Frontend Architecture","\u002Fblog\u002Fmicro-frontend-architecture","blog\u002F006.micro-frontend-architecture",{"title":34,"path":35,"stem":36},"Pre-rendering and Hydration in Vue.js","\u002Fblog\u002Fpre-rendering-and-hydration-in-vuejs","blog\u002F007.pre-rendering-and-hydration-in-vuejs",{"title":38,"path":39,"stem":40},"Satori by Vercel — Dynamic Image Generation in JavaScript","\u002Fblog\u002Fsatori-by-vercel-dynamic-image-generation-in-javascript","blog\u002F008.satori-by-vercel-dynamic-image-generation-in-javascript",{"title":42,"path":43,"stem":44},"Vue.js and Progressive Web Apps (PWA) – Enhancing Web Experiences","\u002Fblog\u002Fvuejs-and-progressive-web-apps-pwa-enhancing-web-experiences","blog\u002F009.vuejs-and-progressive-web-apps-pwa-enhancing-web-experiences",{"title":46,"path":47,"stem":48},"Moving from a Traditional Node.js CRUD API to Serverless Architecture—A Deep Dive","\u002Fblog\u002Fmoving-from-a-traditional-nodejs-crud-api-to-serverless-architecturea-deep-dive","blog\u002F010.moving-from-a-traditional-nodejs-crud-api-to-serverless-architecturea-deep-dive",{"title":50,"path":51,"stem":52},"Nuxt 3 and Serverless Edge Functions—Unlocking Performance and Scalability","\u002Fblog\u002Fnuxt-3-and-serverless-edge-functionsunlocking-performance-and-scalability","blog\u002F011.nuxt-3-and-serverless-edge-functionsunlocking-performance-and-scalability",{"title":54,"path":55,"stem":56},"A Tribute to Asa Bain: Thank You for Everything","\u002Fblog\u002Fa-tribute-to-asa-bain-thank-you-for-everything","blog\u002F012.a-tribute-to-asa-bain-thank-you-for-everything",{"title":58,"path":59,"stem":60},"Migrating JavaScript to TypeScript in ASP.NET MVC Projects","\u002Fblog\u002Fmigrating-javascript-to-typescript-in-aspnet-mvc-projects","blog\u002F013.migrating-javascript-to-typescript-in-aspnet-mvc-projects",{"title":62,"path":63,"stem":64},"Modernizing Classic ASP.NET MVC with Vue.js","\u002Fblog\u002Fmodernizing-classic-aspnet-mvc-with-vuejs","blog\u002F014.modernizing-classic-aspnet-mvc-with-vuejs",{"title":66,"path":67,"stem":68},"Which UI JavaScript Framework Should You Use?","\u002Fblog\u002Fwhich-ui-javascript-framework-should-you-use","blog\u002F015.which-ui-javascript-framework-should-you-use",{"title":70,"path":71,"stem":72},"Vue + AI Integration Workflows: Enhancing Developer Productivity","\u002Fblog\u002Fvue-ai-integration-workflows-enhancing-developer-productivity","blog\u002F016.vue-ai-integration-workflows-enhancing-developer-productivity",{"title":74,"path":75,"stem":76},"OpenAPI Standards & Scalar Integration for Node.js Apps","\u002Fblog\u002Fopenapi-standards-scalar-integration-for-nodejs-apps","blog\u002F017.openapi-standards-scalar-integration-for-nodejs-apps",{"title":78,"path":79,"stem":80},"Nuxt 3.17 — Data Fetching Improvements","\u002Fblog\u002Fnuxt-317-data-fetching-improvements","blog\u002F019.nuxt-317-data-fetching-improvements",{"title":82,"path":83,"stem":84},"Subdomain-Based Multi-Tenancy in Nuxt","\u002Fblog\u002Fsubdomain-based-multi-tenancy-in-nuxt","blog\u002F020.subdomain-based-multi-tenancy-in-nuxt",{"title":86,"path":87,"stem":88},"Type-Safe Backends with TypeScript: tRPC, Zod, and Drizzle ORM","\u002Fblog\u002Ftype-safe-backends-with-typescript-trpc-zod-and-drizzle-orm","blog\u002F021.type-safe-backends-with-typescript-trpc-zod-and-drizzle-orm",{"title":90,"path":91,"stem":92},"Unit Testing Vue Applications with Vitest and Agentic AI","\u002Fblog\u002Funit-testing-vue-applications-with-vitest-and-agentic-ai","blog\u002F022.unit-testing-vue-applications-with-vitest-and-agentic-ai",{"title":94,"path":95,"stem":96},"Hidden Features & Lesser-Known TypeScript Gems","\u002Fblog\u002Fhidden-features-lesser-known-typescript-gems","blog\u002F023.hidden-features-lesser-known-typescript-gems",{"title":98,"path":99,"stem":100},"Nuxt\u002FVercel Acquisition and Its Impact on NuxtHub Users","\u002Fblog\u002Fnuxtvercel-acquisition-and-its-impact-on-nuxthub-users","blog\u002F024.nuxtvercel-acquisition-and-its-impact-on-nuxthub-users",{"title":102,"path":103,"stem":104},"State of Vue & Nuxt Ecosystem 2025","\u002Fblog\u002Fstate-of-vue-nuxt-ecosystem-2025","blog\u002F025.state-of-vue-nuxt-ecosystem-2025",{"title":106,"path":107,"stem":108},"Feature Adoption in TypeScript Over Time","\u002Fblog\u002Ffeature-adoption-in-typescript-over-time","blog\u002F026.feature-adoption-in-typescript-over-time",{"title":110,"path":111,"stem":112},"Migrating From WordPress to Nuxt Content & Using Nuxt Studio","\u002Fblog\u002Fmigrating-from-wordpress-to-nuxt-content-using-nuxt-studio","blog\u002F027.migrating-from-wordpress-to-nuxt-content-using-nuxt-studio",{"title":114,"path":115,"stem":116},"Strategic Topic: The “Rust-ification” of Tooling (Biome & Rolldown)","\u002Fblog\u002Fstrategic-topic-the-rust-ification-of-tooling-biome-rolldown","blog\u002F028.strategic-topic-the-rust-ification-of-tooling-biome-rolldown",{"title":118,"path":119,"stem":120},"Nuxt 4 and the Evolving Full-Stack Framework Landscape","\u002Fblog\u002Fnuxt-4-and-the-evolving-full-stack-framework-landscape","blog\u002F029.nuxt-4-and-the-evolving-full-stack-framework-landscape",{"title":122,"path":123,"stem":124},"Bun as a JavaScript Runtime: Evaluating Readiness Beyond Node.js","\u002Fblog\u002Fbun-as-a-javascript-runtime-evaluating-readiness-beyond-nodejs","blog\u002F030.bun-as-a-javascript-runtime-evaluating-readiness-beyond-nodejs",{"title":126,"path":127,"stem":128},"Top 10 Nuxt Modules That Supercharge Your App From Day One Introduction","\u002Fblog\u002Ftop-10-nuxt-modules-that-supercharge-your-app-from-day-one-introduction","blog\u002F031.top-10-nuxt-modules-that-supercharge-your-app-from-day-one-introduction",{"title":130,"path":131,"stem":132},"Strategic Topic: Vite+, VoidZero, and the Future of Frontend Tooling","\u002Fblog\u002Fstrategic-topic-vite-voidzero-and-the-future-of-frontend-tooling","blog\u002F032.strategic-topic-vite-voidzero-and-the-future-of-frontend-tooling",{"title":134,"path":135,"stem":136},"The Future of Time in JavaScript: Transitioning to the Native Temporal API","\u002Fblog\u002Fthe-future-of-time-in-javascript-transitioning-to-the-native-temporal-api","blog\u002F033.the-future-of-time-in-javascript-transitioning-to-the-native-temporal-api",{"title":138,"path":139,"stem":140},"Understanding Hydration Issues in Nuxt and How Nuxt Hints Helps","\u002Fblog\u002Funderstanding-hydration-issues-in-nuxt-and-how-nuxt-hints-helps","blog\u002F034.understanding-hydration-issues-in-nuxt-and-how-nuxt-hints-helps",false,{"id":143,"title":82,"author":144,"body":148,"date":1142,"description":1143,"extension":1144,"image":1145,"meta":1146,"minRead":257,"navigation":1006,"path":83,"seo":1147,"stem":84,"__hash__":1148},"blog\u002Fblog\u002F020.subdomain-based-multi-tenancy-in-nuxt.md",{"name":145,"avatar":146},"Sean Erick C. Ramones",{"src":147,"alt":145},"\u002Favatars\u002Fprofile-image-1.png",{"type":149,"value":150,"toc":1124},"minimark",[151,156,160,165,181,184,188,196,206,210,450,590,592,596,599,771,774,776,780,784,805,809,920,922,926,929,958,960,964,970,1036,1038,1042,1064,1066,1070,1073,1084,1091,1094,1096,1100,1103,1117,1120],[152,153,155],"h2",{"id":154},"what-is-multi-tenancy","What is Multi-Tenancy?",[157,158,159],"p",{},"Multi-tenancy is an architecture where a single app instance serves multiple clients (tenants). Each tenant behaves like it has its own isolated version of the app.",[161,162,164],"h3",{"id":163},"benefits","Benefits",[166,167,168,172,175,178],"ul",{},[169,170,171],"li",{},"Centralized maintenance",[169,173,174],{},"Efficient infrastructure usage",[169,176,177],{},"Per-tenant custom branding",[169,179,180],{},"Easier scaling and onboarding",[182,183],"hr",{},[152,185,187],{"id":186},"subdomain-based-tenancy-in-nuxt","Subdomain-Based Tenancy in Nuxt",[157,189,190,191,195],{},"You might have seen urls like this in ",[192,193,194],"code",{},"Jira, Github, Slack,"," etc. Each tenant accesses the platform via their own subdomain, like:",[197,198,203],"pre",{"className":199,"code":201,"language":202},[200],"language-text","mllr.preesh.com\nacme.preesh.com\nsome-other-org.preesh.com\n","text",[192,204,201],{"__ignoreMap":205},"",[161,207,209],{"id":208},"tenant-middleware-nuxt-3","Tenant Middleware (Nuxt 3)",[197,211,215],{"className":212,"code":213,"language":214,"meta":205,"style":205},"language-tsx shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F middleware\u002Ftenant.ts\nexport default defineNuxtRouteMiddleware(() => {\n  const host = useRequestHeaders()['host'] || '';\n  const [subdomain] = host.split('.');\n  const knownTenants = useRuntimeConfig().public.knownTenants || [];\n  if (!knownTenants.includes(subdomain)) return navigateTo('\u002Finvalid-tenant');\n  useState('tenant', () => subdomain);\n});\n","tsx",[192,216,217,226,255,295,332,365,410,440],{"__ignoreMap":205},[218,219,222],"span",{"class":220,"line":221},"line",1,[218,223,225],{"class":224},"sHwdD","\u002F\u002F middleware\u002Ftenant.ts\n",[218,227,229,233,236,240,244,248,252],{"class":220,"line":228},2,[218,230,232],{"class":231},"s7zQu","export",[218,234,235],{"class":231}," default",[218,237,239],{"class":238},"s2Zo4"," defineNuxtRouteMiddleware",[218,241,243],{"class":242},"sTEyZ","(",[218,245,247],{"class":246},"sMK4o","()",[218,249,251],{"class":250},"spNyl"," =>",[218,253,254],{"class":246}," {\n",[218,256,258,261,264,267,270,274,277,281,283,286,289,292],{"class":220,"line":257},3,[218,259,260],{"class":250},"  const",[218,262,263],{"class":242}," host",[218,265,266],{"class":246}," =",[218,268,269],{"class":238}," useRequestHeaders",[218,271,273],{"class":272},"swJcz","()[",[218,275,276],{"class":246},"'",[218,278,280],{"class":279},"sfazB","host",[218,282,276],{"class":246},[218,284,285],{"class":272},"] ",[218,287,288],{"class":246},"||",[218,290,291],{"class":246}," ''",[218,293,294],{"class":246},";\n",[218,296,298,300,303,306,309,311,313,316,319,321,323,325,327,330],{"class":220,"line":297},4,[218,299,260],{"class":250},[218,301,302],{"class":246}," [",[218,304,305],{"class":242},"subdomain",[218,307,308],{"class":246},"]",[218,310,266],{"class":246},[218,312,263],{"class":242},[218,314,315],{"class":246},".",[218,317,318],{"class":238},"split",[218,320,243],{"class":272},[218,322,276],{"class":246},[218,324,315],{"class":279},[218,326,276],{"class":246},[218,328,329],{"class":272},")",[218,331,294],{"class":246},[218,333,335,337,340,342,345,347,349,352,354,357,360,363],{"class":220,"line":334},5,[218,336,260],{"class":250},[218,338,339],{"class":242}," knownTenants",[218,341,266],{"class":246},[218,343,344],{"class":238}," useRuntimeConfig",[218,346,247],{"class":272},[218,348,315],{"class":246},[218,350,351],{"class":242},"public",[218,353,315],{"class":246},[218,355,356],{"class":242},"knownTenants",[218,358,359],{"class":246}," ||",[218,361,362],{"class":272}," []",[218,364,294],{"class":246},[218,366,368,371,374,377,379,381,384,386,388,391,394,397,399,401,404,406,408],{"class":220,"line":367},6,[218,369,370],{"class":231},"  if",[218,372,373],{"class":272}," (",[218,375,376],{"class":246},"!",[218,378,356],{"class":242},[218,380,315],{"class":246},[218,382,383],{"class":238},"includes",[218,385,243],{"class":272},[218,387,305],{"class":242},[218,389,390],{"class":272},")) ",[218,392,393],{"class":231},"return",[218,395,396],{"class":238}," navigateTo",[218,398,243],{"class":272},[218,400,276],{"class":246},[218,402,403],{"class":279},"\u002Finvalid-tenant",[218,405,276],{"class":246},[218,407,329],{"class":272},[218,409,294],{"class":246},[218,411,413,416,418,420,423,425,428,431,433,436,438],{"class":220,"line":412},7,[218,414,415],{"class":238},"  useState",[218,417,243],{"class":272},[218,419,276],{"class":246},[218,421,422],{"class":279},"tenant",[218,424,276],{"class":246},[218,426,427],{"class":246},",",[218,429,430],{"class":246}," ()",[218,432,251],{"class":250},[218,434,435],{"class":242}," subdomain",[218,437,329],{"class":272},[218,439,294],{"class":246},[218,441,443,446,448],{"class":220,"line":442},8,[218,444,445],{"class":246},"}",[218,447,329],{"class":242},[218,449,294],{"class":246},[197,451,453],{"className":212,"code":452,"language":214,"meta":205,"style":205},"\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  routeRules: {\n    '\u002F*': { middleware: ['tenant'] },\n  },\n  runtimeConfig: {\n    public: {\n      knownTenants: ['acme', 'globex']\n    }\n  }\n});\n",[192,454,455,460,474,484,517,522,531,540,569,575,581],{"__ignoreMap":205},[218,456,457],{"class":220,"line":221},[218,458,459],{"class":224},"\u002F\u002F nuxt.config.ts\n",[218,461,462,464,466,469,471],{"class":220,"line":228},[218,463,232],{"class":231},[218,465,235],{"class":231},[218,467,468],{"class":238}," defineNuxtConfig",[218,470,243],{"class":242},[218,472,473],{"class":246},"{\n",[218,475,476,479,482],{"class":220,"line":257},[218,477,478],{"class":272},"  routeRules",[218,480,481],{"class":246},":",[218,483,254],{"class":246},[218,485,486,489,492,494,496,499,502,504,506,508,510,512,514],{"class":220,"line":297},[218,487,488],{"class":246},"    '",[218,490,491],{"class":272},"\u002F*",[218,493,276],{"class":246},[218,495,481],{"class":246},[218,497,498],{"class":246}," {",[218,500,501],{"class":272}," middleware",[218,503,481],{"class":246},[218,505,302],{"class":242},[218,507,276],{"class":246},[218,509,422],{"class":279},[218,511,276],{"class":246},[218,513,285],{"class":242},[218,515,516],{"class":246},"},\n",[218,518,519],{"class":220,"line":334},[218,520,521],{"class":246},"  },\n",[218,523,524,527,529],{"class":220,"line":367},[218,525,526],{"class":272},"  runtimeConfig",[218,528,481],{"class":246},[218,530,254],{"class":246},[218,532,533,536,538],{"class":220,"line":412},[218,534,535],{"class":272},"    public",[218,537,481],{"class":246},[218,539,254],{"class":246},[218,541,542,545,547,549,551,554,556,558,561,564,566],{"class":220,"line":442},[218,543,544],{"class":272},"      knownTenants",[218,546,481],{"class":246},[218,548,302],{"class":242},[218,550,276],{"class":246},[218,552,553],{"class":279},"acme",[218,555,276],{"class":246},[218,557,427],{"class":246},[218,559,560],{"class":246}," '",[218,562,563],{"class":279},"globex",[218,565,276],{"class":246},[218,567,568],{"class":242},"]\n",[218,570,572],{"class":220,"line":571},9,[218,573,574],{"class":246},"    }\n",[218,576,578],{"class":220,"line":577},10,[218,579,580],{"class":246},"  }\n",[218,582,584,586,588],{"class":220,"line":583},11,[218,585,445],{"class":246},[218,587,329],{"class":242},[218,589,294],{"class":246},[182,591],{},[152,593,595],{"id":594},"custom-branding-per-tenant","Custom Branding Per Tenant",[157,597,598],{},"We can define per-tenant branding that influences UI themes, logos, and color schemes.",[197,600,602],{"className":212,"code":601,"language":214,"meta":205,"style":205},"\u002F\u002F composables\u002FuseTenantConfig.ts\nexport const useTenantConfig = () => {\n  const tenant = useState('tenant');\n  const map = {\n    acme: { color: '#FF5733', logo: '\u002Facme-logo.svg' },\n    globex: { color: '#3333FF', logo: '\u002Fglobex-logo.svg' }\n  }; \u002F\u002F Ideally pulled from a DB or config API\n  return map[tenant.value];\n};\n",[192,603,604,609,628,652,663,701,737,745,766],{"__ignoreMap":205},[218,605,606],{"class":220,"line":221},[218,607,608],{"class":224},"\u002F\u002F composables\u002FuseTenantConfig.ts\n",[218,610,611,613,616,619,622,624,626],{"class":220,"line":228},[218,612,232],{"class":231},[218,614,615],{"class":250}," const",[218,617,618],{"class":242}," useTenantConfig ",[218,620,621],{"class":246},"=",[218,623,430],{"class":246},[218,625,251],{"class":250},[218,627,254],{"class":246},[218,629,630,632,635,637,640,642,644,646,648,650],{"class":220,"line":257},[218,631,260],{"class":250},[218,633,634],{"class":242}," tenant",[218,636,266],{"class":246},[218,638,639],{"class":238}," useState",[218,641,243],{"class":272},[218,643,276],{"class":246},[218,645,422],{"class":279},[218,647,276],{"class":246},[218,649,329],{"class":272},[218,651,294],{"class":246},[218,653,654,656,659,661],{"class":220,"line":297},[218,655,260],{"class":250},[218,657,658],{"class":242}," map",[218,660,266],{"class":246},[218,662,254],{"class":246},[218,664,665,668,670,672,675,677,679,682,684,686,689,691,693,696,698],{"class":220,"line":334},[218,666,667],{"class":272},"    acme",[218,669,481],{"class":246},[218,671,498],{"class":246},[218,673,674],{"class":272}," color",[218,676,481],{"class":246},[218,678,560],{"class":246},[218,680,681],{"class":279},"#FF5733",[218,683,276],{"class":246},[218,685,427],{"class":246},[218,687,688],{"class":272}," logo",[218,690,481],{"class":246},[218,692,560],{"class":246},[218,694,695],{"class":279},"\u002Facme-logo.svg",[218,697,276],{"class":246},[218,699,700],{"class":246}," },\n",[218,702,703,706,708,710,712,714,716,719,721,723,725,727,729,732,734],{"class":220,"line":367},[218,704,705],{"class":272},"    globex",[218,707,481],{"class":246},[218,709,498],{"class":246},[218,711,674],{"class":272},[218,713,481],{"class":246},[218,715,560],{"class":246},[218,717,718],{"class":279},"#3333FF",[218,720,276],{"class":246},[218,722,427],{"class":246},[218,724,688],{"class":272},[218,726,481],{"class":246},[218,728,560],{"class":246},[218,730,731],{"class":279},"\u002Fglobex-logo.svg",[218,733,276],{"class":246},[218,735,736],{"class":246}," }\n",[218,738,739,742],{"class":220,"line":412},[218,740,741],{"class":246},"  };",[218,743,744],{"class":224}," \u002F\u002F Ideally pulled from a DB or config API\n",[218,746,747,750,752,755,757,759,762,764],{"class":220,"line":442},[218,748,749],{"class":231},"  return",[218,751,658],{"class":242},[218,753,754],{"class":272},"[",[218,756,422],{"class":242},[218,758,315],{"class":246},[218,760,761],{"class":242},"value",[218,763,308],{"class":272},[218,765,294],{"class":246},[218,767,768],{"class":220,"line":571},[218,769,770],{"class":246},"};\n",[157,772,773],{},"Use this config in layout and theming logic for dynamic styles and assets.",[182,775],{},[152,777,779],{"id":778},"authentication-and-role-management","Authentication and Role Management",[161,781,783],{"id":782},"suggested-roles","Suggested Roles",[166,785,786,793,799],{},[169,787,788,792],{},[789,790,791],"strong",{},"HR Admin"," – Full platform access",[169,794,795,798],{},[789,796,797],{},"Manager"," – Can send cards to direct reports",[169,800,801,804],{},[789,802,803],{},"Employee"," – Read-only access to received cards",[161,806,808],{"id":807},"role-based-access-middleware","Role-Based Access Middleware",[197,810,812],{"className":212,"code":811,"language":214,"meta":205,"style":205},"\u002F\u002F middleware\u002Frole.ts\nexport default defineNuxtRouteMiddleware(() => {\n  const user = useState('user').value;\n  if (!user || user.role !== 'HR_ADMIN') return navigateTo('\u002Funauthorized');\n});\n",[192,813,814,819,835,863,912],{"__ignoreMap":205},[218,815,816],{"class":220,"line":221},[218,817,818],{"class":224},"\u002F\u002F middleware\u002Frole.ts\n",[218,820,821,823,825,827,829,831,833],{"class":220,"line":228},[218,822,232],{"class":231},[218,824,235],{"class":231},[218,826,239],{"class":238},[218,828,243],{"class":242},[218,830,247],{"class":246},[218,832,251],{"class":250},[218,834,254],{"class":246},[218,836,837,839,842,844,846,848,850,853,855,857,859,861],{"class":220,"line":257},[218,838,260],{"class":250},[218,840,841],{"class":242}," user",[218,843,266],{"class":246},[218,845,639],{"class":238},[218,847,243],{"class":272},[218,849,276],{"class":246},[218,851,852],{"class":279},"user",[218,854,276],{"class":246},[218,856,329],{"class":272},[218,858,315],{"class":246},[218,860,761],{"class":242},[218,862,294],{"class":246},[218,864,865,867,869,871,873,875,877,879,882,885,887,890,892,895,897,899,901,903,906,908,910],{"class":220,"line":297},[218,866,370],{"class":231},[218,868,373],{"class":272},[218,870,376],{"class":246},[218,872,852],{"class":242},[218,874,359],{"class":246},[218,876,841],{"class":242},[218,878,315],{"class":246},[218,880,881],{"class":242},"role",[218,883,884],{"class":246}," !==",[218,886,560],{"class":246},[218,888,889],{"class":279},"HR_ADMIN",[218,891,276],{"class":246},[218,893,894],{"class":272},") ",[218,896,393],{"class":231},[218,898,396],{"class":238},[218,900,243],{"class":272},[218,902,276],{"class":246},[218,904,905],{"class":279},"\u002Funauthorized",[218,907,276],{"class":246},[218,909,329],{"class":272},[218,911,294],{"class":246},[218,913,914,916,918],{"class":220,"line":334},[218,915,445],{"class":246},[218,917,329],{"class":242},[218,919,294],{"class":246},[182,921],{},[152,923,925],{"id":924},"security-strategies","Security Strategies",[157,927,928],{},"To prevent cross-tenant data leakage and protect user data:",[166,930,931,940,946,952],{},[169,932,933,936,937],{},[789,934,935],{},"Tenant Isolation",": Scope all database queries by ",[192,938,939],{},"tenant_id",[169,941,942,945],{},[789,943,944],{},"Subdomain Cookies",": Use tenant-specific cookies or headers",[169,947,948,951],{},[789,949,950],{},"Rate Limiting",": Apply tenant-specific rate limits",[169,953,954,957],{},[789,955,956],{},"Session Protection",": Do not share session tokens across tenants",[182,959],{},[152,961,963],{"id":962},"database-schema-design","Database Schema Design",[157,965,966,967,969],{},"Each key entity must include a ",[192,968,939],{}," to isolate data:",[197,971,975],{"className":972,"code":973,"language":974,"meta":205,"style":205},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","CREATE TABLE users (\n  id UUID PRIMARY KEY,\n  tenant_id UUID NOT NULL,\n  role TEXT\n);\n\nCREATE TABLE cards (\n  id UUID PRIMARY KEY,\n  tenant_id UUID NOT NULL,\n  created_by UUID,\n  message TEXT\n);\n","sql",[192,976,977,982,987,992,997,1002,1008,1013,1017,1021,1026,1031],{"__ignoreMap":205},[218,978,979],{"class":220,"line":221},[218,980,981],{},"CREATE TABLE users (\n",[218,983,984],{"class":220,"line":228},[218,985,986],{},"  id UUID PRIMARY KEY,\n",[218,988,989],{"class":220,"line":257},[218,990,991],{},"  tenant_id UUID NOT NULL,\n",[218,993,994],{"class":220,"line":297},[218,995,996],{},"  role TEXT\n",[218,998,999],{"class":220,"line":334},[218,1000,1001],{},");\n",[218,1003,1004],{"class":220,"line":367},[218,1005,1007],{"emptyLinePlaceholder":1006},true,"\n",[218,1009,1010],{"class":220,"line":412},[218,1011,1012],{},"CREATE TABLE cards (\n",[218,1014,1015],{"class":220,"line":442},[218,1016,986],{},[218,1018,1019],{"class":220,"line":571},[218,1020,991],{},[218,1022,1023],{"class":220,"line":577},[218,1024,1025],{},"  created_by UUID,\n",[218,1027,1028],{"class":220,"line":583},[218,1029,1030],{},"  message TEXT\n",[218,1032,1034],{"class":220,"line":1033},12,[218,1035,1001],{},[182,1037],{},[152,1039,1041],{"id":1040},"deployment-notes","Deployment Notes",[166,1043,1044,1050,1061],{},[169,1045,1046,1047],{},"Set up wildcard DNS: ",[192,1048,1049],{},"*.preesh.com",[169,1051,1052,1053,1056,1057,1060],{},"Use platforms like ",[789,1054,1055],{},"Vercel"," or ",[789,1058,1059],{},"Netlify"," for dynamic subdomain routing",[169,1062,1063],{},"Automate initial tenant provisioning (branding config, database seeding)",[182,1065],{},[152,1067,1069],{"id":1068},"additional-development-notes","Additional Development Notes",[157,1071,1072],{},"Nuxt Labs recently joined Vercel, which may improve official support for:",[166,1074,1075,1078,1081],{},[169,1076,1077],{},"Wildcard subdomain routing",[169,1079,1080],{},"Scheduled jobs or background workers",[169,1082,1083],{},"Tenant-based static rendering and caching",[157,1085,1086,1087,1090],{},"While current tenant configs (e.g., themes, logos) are manually defined, a future enhancement could allow tenants to edit their own configuration dynamically. This can be achieved using ",[789,1088,1089],{},"Nuxt Content"," as a lightweight CMS for managing per-tenant markdown\u002Fconfig content.",[157,1092,1093],{},"We could also allow tenants to add sub-pages, custom messages, or FAQ sections using markdown files per tenant.",[182,1095],{},[152,1097,1099],{"id":1098},"summary","Summary",[157,1101,1102],{},"This multi-tenant Nuxt setup enables Preesh to scale efficiently as a SaaS tool for HR departments, with:",[166,1104,1105,1108,1111,1114],{},[169,1106,1107],{},"Per-tenant isolation via subdomains",[169,1109,1110],{},"Role-based access control",[169,1112,1113],{},"Custom theming and branding",[169,1115,1116],{},"Secure and scalable architecture",[157,1118,1119],{},"Future upgrades may include self-service onboarding, tenant CMS integration, and more flexible customization per company.",[1121,1122,1123],"style",{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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);}",{"title":205,"searchDepth":228,"depth":228,"links":1125},[1126,1129,1132,1133,1137,1138,1139,1140,1141],{"id":154,"depth":228,"text":155,"children":1127},[1128],{"id":163,"depth":257,"text":164},{"id":186,"depth":228,"text":187,"children":1130},[1131],{"id":208,"depth":257,"text":209},{"id":594,"depth":228,"text":595},{"id":778,"depth":228,"text":779,"children":1134},[1135,1136],{"id":782,"depth":257,"text":783},{"id":807,"depth":257,"text":808},{"id":924,"depth":228,"text":925},{"id":962,"depth":228,"text":963},{"id":1040,"depth":228,"text":1041},{"id":1068,"depth":228,"text":1069},{"id":1098,"depth":228,"text":1099},"2025-07-01","*By Sean Erick C. Ramones, Vue SME | JavaScript\u002FTypeScript SME*","md","https:\u002F\u002Fimages.pexels.com\u002Fphotos\u002F34803994\u002Fpexels-photo-34803994.jpeg?auto=compress&cs=tinysrgb&h=650&w=940",{},{"title":82,"description":1143},"ElKJ_l83Odi2tQrP9cJiyPt9zSwPZYdEryXaXfyWA2Y",[1150,1151],{"title":78,"path":79,"stem":80,"description":1143,"children":-1},{"title":86,"path":87,"stem":88,"description":1152,"children":-1},"*By Sean Erick C. Ramones, Vue SME | JavaScript\u002FTypScript SME*",1779638276831]