Migrating from Per-Site Apps to a Multi-Tenant Architecture
This case study explores the shift from per-site applications to a domain-based multi-tenant platform, and how that decision enabled predictable scaling, safer deployments, and faster iteration.
Overview
As our restaurant platform grew from a handful of sites to dozens across multiple markets, we reached a point where our original architecture — one app per restaurant — actively slowed development, increased operational risk, and made consistency harder to enforce.
We transitioned to a multi-tenant architecture where a single codebase serves multiple restaurants, with tenant resolution handled at request time based on domain and configuration.
This change dramatically reduced maintenance overhead, improved deployment reliability, and enabled faster feature delivery without sacrificing per-restaurant customization.
The Original Architecture: One App per Restaurant
Initially, each restaurant had its own application:
- Separate Next.js app per site
- Shared UI and utilities via a monorepo
- Environment variables and configs per app
- Independent builds and deployments
This worked well early on when:
- The number of restaurants was small
- Feature velocity was moderate
- Customization was expected to diverge
Why It Eventually Broke Down
As the platform scaled, several issues emerged.
Operational Overhead
- Dozens of near-identical apps to deploy and monitor
- Bug fixes required redeploying every site
- CI times increased linearly with tenant count
Configuration Drift
- Small “temporary” overrides became permanent
- Behavior subtly differed across restaurants
- Fixes applied to one site were missed in others
Feature Rollout Friction
- New features required coordinated releases
- Partial rollouts were difficult to reason about
- Experimentation increased risk instead of reducing it
False Sense of Isolation
- Backend systems and data models were still shared
- Failures cascaded despite app-level separation
- Duplication created the illusion of safety
By ~30 active restaurants, this model was no longer sustainable.
Goals of the New Architecture
Before redesigning the system, we defined explicit goals.
- Single deployment pipeline regardless of tenant count
- Zero code forks for tenant-specific behavior
- First-class theming and configuration
- Server-side tenant resolution (no client flashes)
- Safe feature rollouts with predictable behavior
- Minimal migration risk for existing restaurants
The Multi-Tenant Architecture
High-Level Design
- One Next.js application
- Tenants resolved by request domain
- Centralized tenant configuration:
- Business ID
- Theme tokens (colors, fonts)
- Feature flags
- SEO metadata
- Shared UI and logic parameterized by tenant config
Request
→ Domain Resolver
→ Tenant Config
→ SSR Render
→ Cached Output
Why Domain-Based Tenancy
We chose root-domain tenancy instead of subdomains or path-based routing.
Reasons:
- Restaurants already owned their domains
- SEO and branding required first-class domains
- Avoided client-side routing hacks
Tenant resolution happens server-side, before any React render, ensuring:
- No layout flashing
- Correct metadata on first paint
- Predictable caching behavior
Key Technical Decisions
Configuration Over Forks
Instead of branching logic or tenant-specific components:
- All differences live in configuration
- UI behavior is driven by flags, not conditionals
- Theming uses design tokens instead of CSS overrides
This keeps the codebase:
- Predictable
- Reviewable
- Refactor-friendly
Shared Code, Isolated State
Tenants share:
- UI components
- Data-fetching logic
- Build artifacts
Tenants do not share:
- Cached data
- Runtime configuration
- Business identifiers
This balanced efficiency with correctness.
SSR-First Tenant Resolution
Tenant configuration is resolved:
- On the server
- At request time
- Before any React render
This avoids:
- Client-side “loading tenant” states
- Incorrect SEO metadata
- Layout shifts from late-applied themes
Gradual Migration Strategy
We intentionally avoided a “big bang” rewrite.
- New restaurants launched on the multi-tenant platform
- Existing sites migrated incrementally
- Traffic validated tenant-by-tenant
This reduced risk and allowed real-world validation.
Tradeoffs and Alternatives Considered
Separate Apps per Tenant
Pros
- Strong isolation
- Simple mental model early on
Cons
- Poor operational scalability
- Duplication hides real complexity
- Hard to enforce standards
Micro-Frontends
Pros
- Explicit boundaries
- Team autonomy
Cons
- Added complexity without solving the core problem
- Overkill for tenant-based differentiation
- Higher runtime and tooling cost
Subdomain-Based Tenancy
Pros
- Simpler DNS routing
Cons
- Poor fit for restaurant branding
- SEO complications
- Inconsistent customer experience
Results
After migrating to the multi-tenant architecture:
Bug fixes, feature releases, and experiments now reach all tenants immediately, without coordination overhead.
What I’d Do Differently Today
- Introduce tenant-scoped observability earlier
- Formalize configuration validation sooner
- Automate migration safety checks up front
The core architectural decision itself has aged well.

