[{"data":1,"prerenderedAt":683},["ShallowReactive",2],{"navigation_en":3,"/blog/nuxt-multi-tenant-saas-architecture-patterns-that-scale-data":144},[4,16,91,117,127],{"title":5,"path":6,"stem":7,"children":8,"icon":15},"Getting Started","/docs/get-started","docs/1.get-started/1.index",[9,11],{"title":10,"path":6,"stem":7},"Introduction",{"title":12,"path":13,"stem":14},"Setup","/docs/get-started/setup","docs/1.get-started/2.setup","mage:fast-forward-fill",{"title":17,"icon":18,"path":19,"stem":20,"children":21,"page":90},"Features","ic:baseline-cable","/docs/features","docs/2.features",[22,26,30,34,38,42,46,50,54,58,62,66,70,74,78,82,86],{"title":23,"path":24,"stem":25},"Database","/docs/features/database","docs/2.features/01.database",{"title":27,"path":28,"stem":29},"Authentication","/docs/features/auth","docs/2.features/02.auth",{"title":31,"path":32,"stem":33},"Payments","/docs/features/payments","docs/2.features/03.payments",{"title":35,"path":36,"stem":37},"Admin Panel","/docs/features/admin-panel","docs/2.features/04.admin-panel",{"title":39,"path":40,"stem":41},"AI","/docs/features/ai","docs/2.features/05.ai",{"title":43,"path":44,"stem":45},"Customization","/docs/features/customization","docs/2.features/06.customization",{"title":47,"path":48,"stem":49},"Storage","/docs/features/storage","docs/2.features/07.storage",{"title":51,"path":52,"stem":53},"Email","/docs/features/email","docs/2.features/08.email",{"title":55,"path":56,"stem":57},"Languages","/docs/features/languages","docs/2.features/09.languages",{"title":59,"path":60,"stem":61},"SEO","/docs/features/seo","docs/2.features/10.seo",{"title":63,"path":64,"stem":65},"Blogs","/docs/features/blogs","docs/2.features/11.blogs",{"title":67,"path":68,"stem":69},"Documentation","/docs/features/documentation","docs/2.features/12.documentation",{"title":71,"path":72,"stem":73},"Cron Jobs","/docs/features/cron-jobs","docs/2.features/13.cron-jobs",{"title":75,"path":76,"stem":77},"Error Handling","/docs/features/error-handling","docs/2.features/14.error-handling",{"title":79,"path":80,"stem":81},"Analytics","/docs/features/analytics","docs/2.features/15.analytics",{"title":83,"path":84,"stem":85},"PWA","/docs/features/pwa","docs/2.features/15.pwa",{"title":87,"path":88,"stem":89},"Customer Support","/docs/features/customer-support","docs/2.features/16.customer-support",false,{"title":92,"icon":93,"path":94,"stem":95,"children":96,"page":90},"Tutorials","ic:baseline-library-books","/docs/tutorials","docs/3.tutorials",[97,101,105,109,113],{"title":98,"path":99,"stem":100},"Project Structure","/docs/tutorials/project-structure","docs/3.tutorials/1.project-structure",{"title":102,"path":103,"stem":104},"Page Routes","/docs/tutorials/page-routes","docs/3.tutorials/2.page-routes",{"title":106,"path":107,"stem":108},"API Calls","/docs/tutorials/api-calls","docs/3.tutorials/3.api-calls",{"title":110,"path":111,"stem":112},"State Management","/docs/tutorials/state-management","docs/3.tutorials/4.state-management",{"title":114,"path":115,"stem":116},"Legal Pages by GPT","/docs/tutorials/legal-pages-by-gpt","docs/3.tutorials/5.legal-pages-by-gpt",{"title":118,"icon":119,"path":120,"stem":121,"children":122,"page":90},"Formatting & Linting","ic:baseline-format-align-left","/docs/formatting-and-linting","docs/4.formatting-and-linting",[123],{"title":124,"path":125,"stem":126},"Overview","/docs/formatting-and-linting/overview","docs/4.formatting-and-linting/1.overview",{"title":128,"icon":129,"path":130,"stem":131,"children":132,"page":90},"Deployment","ic:baseline-rocket-launch","/docs/deployment","docs/5.deployment",[133,136,140],{"title":124,"path":134,"stem":135},"/docs/deployment/overview","docs/5.deployment/1.overview",{"title":137,"path":138,"stem":139},"Vercel","/docs/deployment/vercel","docs/5.deployment/2.vercel",{"title":141,"path":142,"stem":143},"Cloudflare Workers","/docs/deployment/cloudflare","docs/5.deployment/3.cloudflare",{"post":145,"surround":672},{"id":146,"title":147,"alternates":148,"authors":149,"badge":155,"body":157,"date":651,"description":167,"extension":652,"head":653,"hero_image_url":166,"json_ld":654,"meta":655,"navigation":662,"ogImage":653,"outbound_links":663,"path":664,"primary_keyword":657,"related_articles":665,"robots":653,"schemaOrg":653,"search_intent":666,"seo":667,"sitemap":668,"stem":669,"supporting_keywords":670,"__hash__":671},"blog_en/blog/nuxt-multi-tenant-saas-architecture-patterns-that-scale.md","Nuxt multi-tenant SaaS architecture patterns that scale",[],[150],{"name":151,"to":152,"avatar":153},"Tom Han","https://x.com/tomhan245",{"src":154},"https://cdn.shipahe.ad/tomhan.webp",{"label":156},"Ultimate guide",{"type":158,"value":159,"toc":620},"minimark",[160,169,173,176,181,184,189,207,211,266,270,297,301,304,308,318,322,328,332,357,361,367,371,412,416,437,441,458,462,465,469,500,504,518,522,533,537,540,544,555,559,570,574,588,594,598],[161,162,163],"figure",{},[164,165],"img",{"src":166,"alt":167,"style":168},"https://shipahe.ad/images/blog/nuxt-multi-tenant-saas-architecture-patterns-that-scale/post-342.webp","A practical guide to nuxt multi-tenant SaaS design. Learn viable routing, shared-DB isolation, billing, and testing patterns that scale without surprises.","max-width:100%;border-radius:12px",[170,171,172],"p",{},"Building a multi-tenant SaaS on Nuxt 3 and Nitro is a sequence of high-leverage decisions. Get tenant identity right, scope every request, enforce isolation in the database, and wire billing to entitlements. Do this early and you can ship in weeks, then scale tenants, traffic, and teams without rewrites.",[170,174,175],{},"This guide gives concrete patterns you can implement in a new Nuxt app or layer onto an existing codebase. Each choice is framed so it is easy to ship now and safe to evolve later.",[177,178,180],"h2",{"id":179},"tenancy-foundations-that-scale-in-nuxt","Tenancy foundations that scale in Nuxt",[170,182,183],{},"Multi-tenant means many customers share one application stack. You trade some isolation for speed and cost efficiency. Start simple, but design the seams so you can increase isolation as needs grow.",[185,186,188],"h3",{"id":187},"single-tenant-vs-multi-tenant-at-a-glance","Single-tenant vs multi-tenant at a glance",[190,191,192,193,192,201],"ul",{},"\n  ",[194,195,196,200],"li",{},[197,198,199],"strong",{},"Single-tenant:"," One customer per stack. Maximum isolation, high cost. Choose it only when contracts or data residency make it mandatory.",[194,202,203,206],{},[197,204,205],{},"Multi-tenant:"," Many customers on one stack. Lower cost, more complexity. The default for most early-stage Nuxt apps.",[185,208,210],{"id":209},"tenant-identity-drives-everything","Tenant identity drives everything",[190,212,192,213,192,239,192,257,192,263],{},[194,214,215,216,220,221,224,225,228,229,228,232,228,235,238],{},"Create a first-class ",[217,218,219],"code",{},"tenants"," table with ",[217,222,223],{},"id"," (UUID), ",[217,226,227],{},"slug",", ",[217,230,231],{},"plan",[217,233,234],{},"status",[217,236,237],{},"trial_ends_at",", and timestamps.",[194,240,241,242,245,246,228,249,252,253,256],{},"Model membership with a join table like ",[217,243,244],{},"tenant_users"," holding ",[217,247,248],{},"tenant_id",[217,250,251],{},"user_id",", and ",[217,254,255],{},"role"," (owner, admin, member, viewer).",[194,258,259,260,262],{},"Every tenant-owned record carries ",[217,261,248],{},". Keep truly global tables separate.",[194,264,265],{},"Expose tenant context in both the client (a composable) and the server (request-scoped object) so every layer can enforce it.",[185,267,269],{"id":268},"isolation-levels-you-can-evolve-through","Isolation levels you can evolve through",[190,271,192,272,192,285,192,291],{},[194,273,274,277,278,280,281,284],{},[197,275,276],{},"Row-level in a shared database:"," Single Postgres instance, tables carry ",[217,279,248],{},". Use composite unique indexes like ",[217,282,283],{},"(tenant_id, slug)",". Consider Row Level Security policies to enforce scoping in the database. This is the fastest path to production.",[194,286,287,290],{},[197,288,289],{},"Schema-per-tenant:"," Still one database server, each tenant has its own schema. Stronger blast radius control but more migrations and connection juggling.",[194,292,293,296],{},[197,294,295],{},"Database-per-tenant:"," Highest isolation short of single-tenant. Operationally heavy. Adopt for regulated customers or very large tenants.",[177,298,300],{"id":299},"routing-and-request-scoping","Routing and request scoping",[170,302,303],{},"Your routing model sets cookie scope, perceived brand quality, and how easy observability will be. Choose once, then apply it consistently across SSR, server routes, and static assets.",[185,305,307],{"id":306},"subdomain-per-tenant","Subdomain per tenant",[170,309,310,311,314,315,317],{},"Pattern: ",[217,312,313],{},"acme.example.com",". Parse the host in a Nitro server middleware, resolve the tenant by ",[217,316,227],{},", and attach it to the event context. Benefits: clean session separation, analytics by host, and straightforward custom-domain support later. For custom domains, verify DNS ownership on onboarding, store the mapping, and let your edge terminate TLS per host. Keep cookie domains scoped to the subdomain so tenants cannot see each other’s sessions.",[185,319,321],{"id":320},"path-based-tenancy","Path-based tenancy",[170,323,310,324,327],{},[217,325,326],{},"/t/acme/...",". Resolve the slug from the path and fetch the tenant in a route middleware. It is easy to host anywhere and avoids wildcard DNS. Be strict about internal links always including the tenant segment to prevent cross-tenant navigation. Favor absolute paths that include the resolved slug.",[185,329,331],{"id":330},"tenant-aware-middleware-in-nuxt","Tenant-aware middleware in Nuxt",[190,333,192,334,192,341,192,348,192,351],{},[194,335,336,337,340],{},"Create a small server middleware that resolves ",[217,338,339],{},"tenant"," from host or path and fails closed if not found.",[194,342,343,344,347],{},"Expose a ",[217,345,346],{},"useTenant()"," composable that reads the server-provided context on SSR and hydrates a client store. Do not let components guess.",[194,349,350],{},"Order of operations: resolve tenant, then locale. Keep the i18n switch tenant-aware so language changes never drop the tenant prefix.",[194,352,353,354,356],{},"Protect caching: add a Surrogate-Key or Vary header that includes ",[217,355,248],{}," so edge caches and CDNs cannot bleed content across tenants.",[177,358,360],{"id":359},"data-isolation-and-storage","Data isolation and storage",[170,362,363,364,366],{},"Start with a shared Postgres database and row-level scoping. Treat ",[217,365,248],{}," as non-negotiable and enforce it at the ORM and database levels.",[185,368,370],{"id":369},"modeling-and-constraints","Modeling and constraints",[190,372,192,373,192,386,192,396,192,405],{},[194,374,375,376,378,379,382,383,385],{},"Add ",[217,377,248],{}," to every multi-tenant table. Use composite unique keys for namespaced uniqueness, for example ",[217,380,381],{},"(tenant_id, email)"," or ",[217,384,283],{},".",[194,387,388,389,391,392,395],{},"Prefer foreign keys that include ",[217,390,248],{}," to stop cross-tenant references. A common pattern is a composite FK on ",[217,393,394],{},"(id, tenant_id)"," pairs.",[194,397,398,399,401,402,404],{},"Use a typed ORM and repositories that always require ",[217,400,248],{},". Ban queries that accept only a primary key. Add helpers that auto-stamp ",[217,403,248],{}," on inserts.",[194,406,407,408,411],{},"Design for soft deletes with ",[217,409,410],{},"deleted_at",". Add partial indexes to keep lookups fast while ignoring deleted rows.",[185,413,415],{"id":414},"files-and-storage","Files and storage",[190,417,192,418,192,425,192,431,192,434],{},[194,419,420,421,424],{},"Use S3-compatible storage with a prefix like ",[217,422,423],{},"tenants/{tenant_id}/...",". Never store tenant-owned files at the root.",[194,426,427,428,430],{},"Keep file metadata in the database with ",[217,429,248],{},", size, checksum, and content type. Validate metadata before issuing a download.",[194,432,433],{},"Serve uploads with short-lived presigned URLs and enforce content disposition to avoid leaking file names.",[194,435,436],{},"Apply lifecycle policies per prefix to expire archives and comply with retention rules.",[185,438,440],{"id":439},"scheduled-jobs-and-background-work","Scheduled jobs and background work",[190,442,192,443,192,449,192,452],{},[194,444,445,446,448],{},"Drive cron tasks by iterating tenants in small batches and passing ",[217,447,248],{}," into each job payload. Keep jobs idempotent with an idempotency key per tenant and day.",[194,450,451],{},"Bound retries and isolate noisy tenants by limiting concurrency per tenant. A single failing job must not block the global queue.",[194,453,454,455,457],{},"For long-running work like exports or AI generation, store progress rows keyed by ",[217,456,248],{}," so the UI can poll safely.",[177,459,461],{"id":460},"billing-and-lifecycle-controls","Billing and lifecycle controls",[170,463,464],{},"Without billing, tenancy is a hobby. Bind access to a subscription or one-time purchase and express plan rules as enforceable checks at the server boundary.",[185,466,468],{"id":467},"checkout-entitlements-and-limits","Checkout, entitlements, and limits",[190,470,192,471,192,477,192,497],{},[194,472,473,474,476],{},"Bind subscriptions to ",[217,475,248],{},", not users. A user can belong to multiple tenants with different roles and plans.",[194,478,479,480,483,484,228,487,228,490,252,493,496],{},"Express plan rules in code and data. A simple ",[217,481,482],{},"entitlements"," table with keys like ",[217,485,486],{},"max_users",[217,488,489],{},"max_projects",[217,491,492],{},"ai_tokens_per_month",[217,494,495],{},"file_storage_mb"," makes checks simple and auditable.",[194,498,499],{},"On successful checkout, upsert the subscription, store the current period, and emit a domain event so the app can enable features and send emails.",[185,501,503],{"id":502},"trials-seats-and-usage-based-features","Trials, seats, and usage-based features",[190,505,192,506,192,509,192,512],{},[194,507,508],{},"Track seat counts on the tenant. Enforce limits in the invite flow and surface an upgrade CTA if the cap is reached.",[194,510,511],{},"Handle past-due gracefully. Keep access to data, restrict premium features, and schedule retries. End with a clear downgrade state if payment fails.",[194,513,514,515,517],{},"For AI tools and content generation, meter usage per tenant. Count tokens, requests, or images, store them with ",[217,516,248],{}," and billing period, and cap or throttle when the entitlement is exhausted.",[185,519,521],{"id":520},"admin-controls","Admin controls",[190,523,192,524,192,527,192,530],{},[194,525,526],{},"Provide a staff-only Admin Panel to view tenants, users, subscriptions, and recent events.",[194,528,529],{},"Require elevated roles for dangerous actions like bans and refunds. Log who did what and when for every mutation.",[194,531,532],{},"Allow safe user impersonation for support with a visible banner and automatic revert, recording an audit entry.",[177,534,536],{"id":535},"testing-and-observability-by-tenant","Testing and observability by tenant",[170,538,539],{},"Multi-tenant bugs hide in edges and defaults. Make tests and telemetry tenant-aware from day one.",[185,541,543],{"id":542},"automated-tests-that-assert-isolation","Automated tests that assert isolation",[190,545,192,546,192,549,192,552],{},[194,547,548],{},"Seed at least two tenants in fixtures and run every read and write twice: Tenant A cannot see Tenant B.",[194,550,551],{},"Write end-to-end flows for signup, invite, checkout, upgrade, and downgrade. Tools like FlyTrap help explore flows and generate tests that surface cross-tenant navigation errors.",[194,553,554],{},"Add smoke tests that verify middleware blocks requests without a resolved tenant.",[185,556,558],{"id":557},"feature-flags-and-migrations","Feature flags and migrations",[190,560,192,561,192,564,192,567],{},[194,562,563],{},"Roll out risky features to a subset of tenants. Keep the flag check on the server so it cannot be bypassed by the client.",[194,565,566],{},"Rehearse migrations on a staging snapshot. Include backfills that are idempotent and safe to retry under load.",[194,568,569],{},"Instrument each migration with timing and row counts per tenant so you can spot outliers before they hit production.",[185,571,573],{"id":572},"logs-metrics-and-analytics","Logs, metrics, and analytics",[190,575,192,576,192,582,192,585],{},[194,577,578,579,581],{},"Attach ",[217,580,248],{}," to every log line, trace, and metric. Include the user id and request id for correlation.",[194,583,584],{},"Track per-tenant pageviews, signups, invites, and billable events. Use these to plan capacity and discover churn risks.",[194,586,587],{},"Alert on error rates and latency by tenant so a noisy customer cannot hide global issues, and a global incident is obvious.",[170,589,590,591,593],{},"Want to ship faster without rebuilding basics? A Nuxt SaaS starter kit gives you authentication, protected pages, a typed database with an ORM, i18n with an in-app language switch, an Admin Panel, scheduled jobs, and ready-to-wire one-time or subscription payments keyed to ",[217,592,248],{},". Shipahe.ad packages these into a Nuxt boilerplate with deployment tooling so you can focus on plan limits, data modeling, and the features that make your product valuable.",[177,595,597],{"id":596},"key-takeaways","Key takeaways",[190,599,192,600,192,605,192,608,192,611,192,614],{},[194,601,602,603,385],{},"Decide tenant identity first and expose it in middleware, composables, and the database. Everything hangs on ",[217,604,248],{},[194,606,607],{},"Start with a shared Postgres database and row-level scoping. Evolve to schema or database per tenant only when contracts or scale demand it.",[194,609,610],{},"Pick one routing model and enforce tenant context everywhere. Cache and cookies must be tenant-aware.",[194,612,613],{},"Tie subscriptions to tenants and enforce entitlements in server routes. Be generous with data access during billing issues.",[194,615,616,617,619],{},"Test isolation continuously and stamp ",[217,618,248],{}," on logs, traces, and metrics. Observability makes support and scaling sane.",{"title":621,"searchDepth":622,"depth":622,"links":623},"",2,[624,630,635,640,645,650],{"id":179,"depth":622,"text":180,"children":625},[626,628,629],{"id":187,"depth":627,"text":188},3,{"id":209,"depth":627,"text":210},{"id":268,"depth":627,"text":269},{"id":299,"depth":622,"text":300,"children":631},[632,633,634],{"id":306,"depth":627,"text":307},{"id":320,"depth":627,"text":321},{"id":330,"depth":627,"text":331},{"id":359,"depth":622,"text":360,"children":636},[637,638,639],{"id":369,"depth":627,"text":370},{"id":414,"depth":627,"text":415},{"id":439,"depth":627,"text":440},{"id":460,"depth":622,"text":461,"children":641},[642,643,644],{"id":467,"depth":627,"text":468},{"id":502,"depth":627,"text":503},{"id":520,"depth":627,"text":521},{"id":535,"depth":622,"text":536,"children":646},[647,648,649],{"id":542,"depth":627,"text":543},{"id":557,"depth":627,"text":558},{"id":572,"depth":627,"text":573},{"id":596,"depth":622,"text":597},"2026-07-24","md",null,"[object Object]",{"tags":656},[657,658,659,660,661],"nuxt","saas architecture","multi-tenant","vue","billing",true,[],"/blog/nuxt-multi-tenant-saas-architecture-patterns-that-scale",[],"Informational",{"title":147,"description":167},{"loc":664},"blog/nuxt-multi-tenant-saas-architecture-patterns-that-scale",[657,658,659,660,661],"-nGRmo3G7cbvx_mCunXbQfAUK_Bu1i5iIjaLVFwHLB0",[673,678],{"title":674,"path":675,"stem":676,"description":677,"children":-1},"Nuxt email templates: 7 examples for SaaS notifications","/blog/nuxt-email-templates-7-examples-for-saas-notifications","blog/nuxt-email-templates-7-examples-for-saas-notifications","Seven Nuxt email templates with subject lines, fields, i18n, cron, and deliverability tips. Ship reliable welcomes, receipts, trials, and more from day one.",{"title":679,"path":680,"stem":681,"description":682,"children":-1},"Nuxt SaaS case study: launching an MVP in 10 days","/blog/nuxt-saas-case-study-launching-an-mvp-in-just-10-days","blog/nuxt-saas-case-study-launching-an-mvp-in-just-10-days","How we shipped a paid AI MVP in 10 days with a Nuxt starter kit. Real metrics on traffic, signups, conversions, revenue, and the exact features we used.",1784909513299]