Case Study: Scalable Frontend Architecture for a Multi-Team SaaS Platform
Evidence note:
This case study is a sanitized composite based on real multi-team SaaS architecture work. Team names, product details, and exact figures are generalized. Metrics are shown as bounded ranges so the reader can evaluate the outcome honestly without exposing private company data.
1. Context and Constraints
Product Context
- B2B SaaS platform with marketing pages, authenticated product surfaces, and admin tooling
- Growth pressure on both product breadth and release frequency
- Strong need for UX consistency across multiple product areas
Organizational Context
- 5 frontend teams
- weekly production releases
- backend owned by a separate organization
- no dedicated frontend platform team at the start
Technical Context
- React + Next.js App Router
- mixed rendering strategies
- ad-hoc state management patterns
- shared utilities growing without clear ownership
2. Baseline Problems
Before proposing a target shape, the architecture review found four repeated failure patterns.
Structural
- cross-feature imports were common
- ownership boundaries were hard to explain
- refactors touched more modules than expected
Data and State
- backend-owned data was duplicated in client stores
- cache behavior was inconsistent across teams
- synchronization bugs were recurring in high-change flows
Performance
- dashboards were hydrating more UI than necessary
- bundle growth was not budgeted
- rendering strategy was chosen by habit rather than route needs
Organizational
- architectural decisions lived mostly in Slack and memory
- teams re-litigated the same patterns
- onboarding to the codebase took too long
3. Architecture Goals
The goals were written before the solution:
- reduce future change cost
- let teams work in parallel with lower blast radius
- make boundaries visible and enforceable
- treat performance as a system-level constraint
- preserve decisions through team churn
4. Key Decisions
ADR-001: Feature-Based Structure Over Layered Structure
Decision:
- move to a feature-led organization model with explicit public APIs
Why:
- locality of change mattered more than maximizing early reuse
- layered folders were hiding ownership rather than clarifying it
Accepted cost:
- some early duplication
- more deliberate governance around boundaries
ADR-002: Modular Monolith Over Micro-Frontends
Decision:
- keep a single deployable frontend with enforced internal boundaries
Why:
- teams could still coordinate releases
- shared UX consistency mattered
- runtime simplicity was more valuable than theoretical autonomy
Accepted cost:
- shared release cadence
- stronger need for tooling and compatibility policy
ADR-003: Server-Owned Data Is Not Global Client State
Decision:
- treat backend data as server state with explicit cache ownership
Why:
- duplicated client copies were creating stale-state bugs
- teams needed clearer ownership and invalidation rules
Accepted cost:
- more discipline around fetch orchestration
- more explicit route/data design work up front
ADR-004: Route-Level Rendering Strategy
Decision:
- choose rendering by route and page segment, not by app-wide ideology
Example:
| Surface | Strategy |
|---|---|
| marketing | SSG or cached SSR |
| auth and entry flows | SSR |
| dashboard shell | server-rendered shell plus interactive client regions |
| reporting | SSR with explicit cache strategy |
5. Implementation Shape
High-Level Structure
Enforcement
- import constraints for cross-feature access
- explicit module entry points
- route-level rendering decisions documented
- ADRs and RFCs for durable changes
Governance Additions
- ownership map by feature and shared surface
- migration playbooks for boundary changes
- performance budgets for critical routes
6. Baseline -> Intervention -> Outcome
| Area | Baseline | Intervention | Outcome after two quarters |
|---|---|---|---|
| onboarding | new engineers typically needed 2-3 weeks to navigate major product areas safely | feature boundaries, ownership map, public APIs | onboarding to productive contribution dropped to roughly 1-2 weeks |
| state bugs | repeated stale-data and sync defects in shared workflows | server-state rules plus cache ownership | state-related regressions dropped by an estimated 30-45% |
| bundle growth | bundle size changed without review | route budgets plus review gates | growth stabilized and budget exceptions became explicit instead of accidental |
| architectural debate | repeated disagreement over "best" patterns | ADRs, RFCs, and paved-road defaults | recurring debates reduced materially, especially in new feature kickoff |
7. What Worked
- teams changed features more safely because boundaries were easier to see
- performance conversations moved from taste to budgets
- ownership became easier to explain across product and platform surfaces
- the architecture became easier to teach because its artifacts existed outside the code
8. What I Would Revisit Today
- introduce route-level budget enforcement earlier rather than after bundle growth became visible
- formalize ownership maps before the number of shared utilities accelerated
- add contract-testing around the most volatile backend dependencies sooner
9. Lessons
- modular monoliths are often the right default longer than teams expect
- route-level rendering decisions prevent ideological overreach
- ownership maps are as important as folder structures
- architecture only becomes real when teams can use it without asking permission every day