Public Portfolio Project — Open Source
Multi-Tenant Frontend Monorepo
Overview
An open-source, public architecture demo — a Turborepo and pnpm monorepo showing how two independently deployable tenant apps, Nova and Aurora, share the same package graph (UI, theme, query, API, and feature packages) while running tenant-specific configuration and mock data.
Context
Built as a standalone technical demonstration — not affiliated with any employer or client codebase — to show, end-to-end, how a multi-tenant frontend platform can be architected: shared UI, theme, query, API, and feature packages consumed by two independently deployable tenant apps, each with its own configuration and tenant-aware mock data, all inspectable in a public repository.
Contribution
- Designed and built the full monorepo architecture from scratch using Turborepo and pnpm workspaces
- Built two independently deployable tenant apps, Nova (web-client) and Aurora (tenant-client), sharing the same package graph
- Built eight shared top-level packages spanning UI, theming, configuration, API access, query, and mock data
- Built shared auth-demo and account-demo feature packages consumed with identical implementation by both tenant apps
- Built a tenant-aware mock runtime with stateful session, profile, preferences, and account data — no backend required
- Committed a pnpm-lock.yaml so installs are deterministic and reproducible across the workspace
Technology
- React
- TypeScript
- Vite
- Turborepo
- pnpm Workspaces
- TanStack Query
- Zustand
- Vercel
Confidentiality
Public Portfolio Project — Open Source
Engineering Case Study
Monorepo Architecture
The project is structured as a Turborepo and pnpm workspace monorepo: two independently deployable apps — web-client (Nova) and tenant-client (Aurora) — consume a shared set of internal packages rather than duplicating logic across tenants.
Turborepo's task graph and caching keep builds, linting, and type-checking fast as the workspace grows, while pnpm workspaces keep dependency linking explicit — and a committed pnpm-lock.yaml keeps installs deterministic across machines and CI.
Shared Package Boundaries
Eight top-level packages — ui, theme, config, api, query, mock-api, eslint-config, and typescript-config — form the shared foundation both apps build on, alongside two shared feature packages under packages/features.
Keeping these concerns in separate packages with clear boundaries makes the split between shared platform code and tenant-specific configuration explicit, rather than something that has to be inferred from application code.
Nova & Aurora: Shared Implementation, Tenant-Specific Configuration
Nova (web-client) and Aurora (tenant-client) are two independently deployed apps that share the exact same package graph — UI, theme, query, API, and feature packages — while running distinct branding, configuration, and mock account data.
The two tenants also demonstrate that shared implementation doesn't mean identical runtime data: Nova's mock account carries a $1,250 balance with +300 / -150 / +500 activity under the profile "Nova Demo Account," while Aurora's carries a $2,840 balance with +650 / -220 / +180 activity under "Aurora Demo Account" — same code path, tenant-specific state.


Shared Auth + Account Feature Packages
Authentication and account management live in packages/features/auth-demo and packages/features/account-demo, mounted with identical implementation in both tenant apps rather than reimplemented per app.
Both packages run against real TanStack Query hooks into the mock runtime: two-step login, protected routing, profile, preferences, and account activity all behave like a backend-driven feature, not a static UI mockup. Session state in auth-demo is held in Zustand; the rest of the shared packages compose React context and TanStack Query.
Nova and Aurora render this same component tree from different tenant-aware mock fixtures: Nova shows a Standard membership, a $1,250 balance, and mobile number 555-0100 under "Nova Demo Account"; Aurora shows a Preferred membership, a $2,840 balance, and 555-0142 under "Aurora Demo Account." The implementation isn't duplicated — only the fixture data behind it differs per tenant.


Tenant-Aware Mock Runtime
packages/mock-api stands in for a real backend: a route-matching dispatcher intercepts requests from both apps and returns tenant-specific session, profile, preferences, and account data, so the whole platform runs and is demoable without any external service.
The mock layer holds real in-memory state per tenant, so mutations — updating a profile, toggling a preference — persist for the session the way a backend-driven application would, and Nova and Aurora never see each other's state.
Query + API Abstraction
packages/api wraps native fetch in a minimal client — no Axios or other HTTP library — that both tenant apps and the shared feature packages call through.
packages/query builds typed useQueryApi / useMutationApi hooks on top of TanStack Query, so data fetching, caching, and mutation state are handled consistently across every package that needs them.
Architectural Decisions & Tradeoffs
Splitting functionality into eight top-level packages plus two feature packages adds coordination overhead: dependency management, package boundaries, and build orchestration all get more complex than a single app would require. That overhead is accepted deliberately, because reuse across genuinely independent tenant apps — not reuse in name only — is the point the case study exists to demonstrate.
Turborepo's caching and task graph, pnpm's strict workspace linking, and a committed lockfile are what make that granularity practical instead of just theoretically clean — without them, the coordination cost would outweigh the reuse benefit.