How I Split One Next.js App into Four Products
The architecture decisions that separated Portfolio, Studio, Admin, and Auth while keeping shared contracts and one clear data model.
- Architecture
- Next.js
- Monorepo
- System Design

The system started as one Next.js application because that was the fastest honest architecture for the first version. One repository, one deployment, and almost no ceremony between a public portfolio and the experiments behind it.
Then the products stopped sharing the same needs.
Public editorial pages wanted excellent crawlability and a small client surface. Authenticated tools needed protected data and product navigation. Content operations required stronger authorization. Sign-in, recovery, MFA, and identity management carried risks that did not belong in every application.
The problem was no longer folder organization. One runtime was pretending to be four products.
The boundary appeared in the responsibilities
I wrote the product responsibilities before moving code:
| Product | Owns | Trust model |
|---|---|---|
| Portfolio | Public identity, Work, Writing, Resume, contact | Public and indexable |
| Studio | Products, utilities, personal workspaces | Public discovery plus protected user data |
| Admin | CMS, users, access, deployment operations | Authenticated and capability-gated |
| Auth | Sign-in, recovery, MFA, profile, account security | Conservative identity boundary |
Once each surface could be described in one sentence, the split became a product decision rather than an architectural fashion.
The boundary also reduced accidental coupling. A Studio proxy change no longer decides whether Portfolio can be crawled. An Admin dependency does not enter the public application. Auth return rules have one owner.
The current runtime shape
The monorepo now contains four independent Next.js 16 and React 19 applications. They deploy separately, use application-specific environment contracts, and own their routes and navigation.
They still share one codebase because several contracts are genuinely common:
@jayantgoyal/identityowns the framework-neutral person and product identity.@jayantgoyal/web-brandowns web-facing labels and synchronized asset paths.@jayantgoyal/web-urlsbuilds canonical origins and rewrites cross-product URLs.@jayantgoyal/web-seocentralizes metadata and indexability helpers.@jayantgoyal/web-authcarries Supabase SSR, cookies, session, safe-return, profile, password, and logout contracts.@jayantgoyal/web-uisupplies the shared Studio, Admin, and Auth shell.@jayantgoyal/portfolio-contractskeeps Portfolio reads and Admin writes aligned.
Portfolio deliberately owns its editorial component system. It does not import the application shell used by operational products because the public reading experience has a different visual and runtime responsibility.
Share contracts, not application behavior
The most important monorepo rule is directional: applications may import packages, but never another application’s source.
A shared package earns its place when multiple products depend on a stable contract. It does not absorb a behavior merely because two files look similar.
For example, @jayantgoyal/web-auth knows how to create a server client and validate a safe return URL. Studio and Admin still decide who may enter a route. @jayantgoyal/web-ui can render a sidebar shell, but each product owns its navigation tree. @jayantgoyal/portfolio-contracts defines a Writing record, while Admin owns editing and Portfolio owns public presentation.
That division keeps reuse useful without turning packages into a fifth hidden application.
One database does not mean one data boundary
The four products use the same Supabase project because identity is global and cross-product contracts are real. Ownership is expressed through explicit schemas rather than separate databases created for appearance:
foundationcontains private reusable database primitives;iamcontains profiles, access, roles, capabilities, policy, and audit data;iam_privatecontains private authorization predicates and provisioning helpers;studioowns Studio product data;portfolioowns public CMS, Writing, LinkedIn planning, and contact-abuse state.
Every application selects the intended schema. Row-level security and server authorization preserve resource boundaries. Service-role access is limited to authorized operational routes and never enters Portfolio or Auth.
This was a deliberate middle ground: independent product deployments without artificial identity duplication or cross-database coordination.
Auth was a migration, not a redirect
Moving authentication into its own application affected cookies, callbacks, recovery links, preview hosts, return URLs, and stale sessions. A folder move could not prove those seams were safe.
The cutover extracted shared session contracts first, preserved compatibility entry routes while ownership moved, validated allowed return origins, and tested cross-application behavior before making Auth the interactive owner. The current state is clear: Auth owns sign-in, recovery, MFA, providers, profile, and account security; product entry URLs are redirects, not competing implementations.
That sequence took longer than changing a destination URL. It made the risk visible and reversible.
What the split improved
The result is not simply a cleaner tree.
- Portfolio performance can be measured without authenticated product code in its graph.
- Studio can protect user data while leaving product discovery public.
- Admin can use operation-specific capabilities for privileged work.
- Auth has one conservative security boundary.
- Deployments can fail or roll back independently.
- Shared contract changes still receive monorepo-wide type and architecture checks.
The architecture also made later stories easier to explain. The CMS now has an obvious edit owner and public read owner. Bundle budgets can be enforced per application. A content field has one path from schema to Admin to Portfolio.
The tradeoff I accept
Four applications create more configuration: four deployments, environment contracts, domain mappings, route inventories, and shared-package consumers. The system therefore needs automated checks around identity, URLs, SEO, service-role use, architecture, and documentation.
That cost is worthwhile because it buys a boundary I can name and test. A change has an owner. A product has an audience. Shared code has a reason to be shared.
The architecture became larger only after the product responsibilities became different. That order matters.
Explore Studio, see Admin, or read how the CMS crosses the Admin and Portfolio boundary.