Architecture Patterns Compared
Cross-module catalog: what each pattern is, when to pick it, what to avoid, and a short example. Deep dives live in the linked modules.
1. Track map — where patterns live
What: Patterns are split across modules. Use this page to choose; open the module to implement.
| Concern | Primary patterns | Module |
|---|---|---|
| UI components | Feature slices, composition, presentational split | 1 · Components |
| App structure | Hexagonal, Clean/Onion, Layered, Vertical slice, MVC→MVI | 2 · Application architecture |
| Domain modeling | DDD: ubiquitous language, bounded contexts, aggregates, ACL | 3 · Domain-Driven Design |
| Shell & UI kit | Design tokens, app shell, route-driven layout | 4 · Design systems |
| Client data | Local / URL / store / server-cache taxonomy | 5 · State |
| Remote data | Query + thin client, BFF, cache layers, realtime, versioning | 6 · Data layer |
| Delivery | CSR / SSR / SSG / ISR / streaming / RSC / Islands | 7 · Rendering |
| Org scale | MFE integration, perf budgets | 8 · MFEs |
| Ship quality | Testing pyramid, a11y, flags, observability, log contracts | 9 · Quality |
| Trust | XSS/CSP/CSRF, cookie vs token, privacy | 10 · Security |
Do
- Start here when choosing; open the module for labs and anti-patterns
- Prefer the smallest pattern that meets SEO, team, and deploy constraints
Avoid
- Stacking hexagonal + MFE + event sourcing on day one without a constraint
Example: Greenfield product → Modules 1–6; add Module 7 only when squads need independent deploys.
2. Application structure patterns
What: How the whole app (not just components) is shaped.
| Pattern | Core idea | Prefer when | Skip when |
|---|---|---|---|
| Hexagonal | Domain center; ports + adapters | Non-trivial rules; multiple UIs; testable core | One-line CRUD screens |
| Clean / Onion | Dependencies point inward through layers | Same as hexagonal; long-lived domain | 3-screen SPA ceremony |
| Layered | Presentation → app → domain → infra | Shared infra across many features | Features diverge heavily |
| Vertical slice | Feature owns UI+use case+adapter | Squads ship features independently | Tiny apps with one folder |
| Modular monolith | One deploy, hard module boundaries | Default for most products | True multi-deploy ownership |
| Strangler fig | Replace legacy route-by-route | Rewrites / migrations | Greenfield |
Do
- Default: vertical slices + modular monolith; add hexagonal ports where rules hurt
- Use strangler seams + anti-corruption adapters for legacy
Avoid
- Hexagonal folder trees that still
fetchfromdomain/
Example: placeOrder(cart, orderPort) pure; HttpOrderAdapter at the edge; later extract checkout MFE without rewriting domain.
3. Domain-Driven Design
What: Model software around the business domain — language, bounded contexts, aggregates — not around React folders.
| Idea | Prefer when | Skip when |
|---|---|---|
| Ubiquitous language | Shared terms across UI + API + experts | Throwaway prototypes |
| Bounded contexts | Same word means different things per team | Single tiny CRUD app |
| Aggregates + invariants | Non-trivial rules (pricing, eligibility) | Forms with no domain rules |
| Anti-corruption layer | Legacy / foreign APIs | Clean greenfield contracts |
Do
- Keep domain free of React/
fetch; enforce critical invariants on the server - Translate at context boundaries — no shared god
Producttype
Avoid
- Calling feature folders “DDD” without language or contexts
Example: Catalog Product vs Checkout ProductRef; Order.addItem enforces qty ≥ 1.
Detail: Domain-Driven Design
4. Presentation patterns (MVC → MVI)
What: How view and state updates are separated.
| Pattern | Idea | FE fit |
|---|---|---|
| MVC | Controller handles input | Loose map to page + handlers |
| MVP | Presenter pushes into passive view | Rare in React |
| MVVM | ViewModel + bindings | Vue/Angular; React via stores/selectors |
| MVI | Intent → model → view state | Redux/RTK, complex React screens |
Do
- Prefer MVI-style unidirectional flow for complex screens
- Keep views Storybook-friendly without network
Avoid
- Pattern wars on static marketing pages
Example: Checkout intents (submit) → view-state { status, errors, total } → render.
Detail: Application architecture · §5
5. Component structure patterns
What: How you slice UI ownership and composition.
| Pattern | Use when | Skip when |
|---|---|---|
| Feature-sliced folders | Multiple domains ship independently | Tiny app (< ~10 screens) |
| Presentational / container | Data wiring clutters pure UI | Most hooks-era screens (colocate) |
| Compound components | Shared implicit state (Tabs, Dialog) | One-off layouts |
| Atomic Design | Design-system inventory | As the only app folder strategy |
| Inheritance hierarchies | Almost never in React | Prefer composition |
Do
- Default to feature folders + composition; promote shared UI after ~3 reuses
Avoid
- Global
components/dumping ground with cross-feature imports
Example: features/billing/InvoiceList.tsx composes @org/ui Table.
Detail: Module 1 concepts
6. State patterns compared
What: Where data lives determines sync cost and re-render blast radius.
| Pattern | Owns | Prefer for |
|---|---|---|
Local useState | One component’s UI | Modals, toggles, drafts |
| URL (search/path) | Shareable view state | Filters, tabs, pagination |
| Context | Low-churn tree values | Theme, locale, auth snapshot |
| Zustand / atoms | Client-global UI / feature store | Cart, wizard, chrome |
| TanStack Query | Server cache | Any remote list/detail |
| Redux Toolkit | Large shared client contract | Multi-team legacy / admin |
| XState | Explicit control flow | Multi-step illegal-state flows |
Do
- Classify every field: local → URL → client store → server cache
- Keep server entities in Query
Avoid
- Mirroring API entities into Redux/Zustand by default
Example: Shelf — filters in URL, list from Query, isFilterOpen local.
Detail: State concepts
7. Data-layer & CQRS-lite
What: How the client talks to backends; reads vs writes often diverge.
| Pattern | Role | Prefer for |
|---|---|---|
| TanStack Query | Read model / cache | Default GET/list/detail |
| Mutations / use cases | Commands | Writes + invalidation |
| Thin API client | Auth, retries, errors | Shared fetch wrapper |
| OpenAPI / tRPC / GraphQL codegen | Typed contracts | Teams with a schema |
| Repository | Domain verbs over multi-calls | 2–3 public APIs |
| BFF / Route Handler | Server aggregation | Secrets, joins, shaping |
| Event-driven bus | Cross-widget / MFE signals | Decoupled producers |
Do
- Treat Query as read side and mutations/use cases as write side (CQRS-lite)
- BFF when secrets or joins belong on the server
Avoid
- Caching inside the API client; browser → many microservices
Example: useOrderQuery + usePlaceOrderMutation invalidating ['orders'].
Detail: Data layer · App architecture · CQRS
8. Rendering & delivery patterns
What: Where and when HTML/JS are produced.
| Strategy | Use when | Avoid when |
|---|---|---|
| CSR | Auth shells, heavy interactivity | Public SEO pages |
| SSR | Personalized + indexable | Pure static (prefer SSG) |
| SSG | Stable docs/marketing | Per-user data |
| ISR | Catalog/CMS on a schedule | Hard realtime |
| Streaming SSR | Slow upstream; early shell | No Suspense boundaries |
| RSC + client leaves | Data on server; minimal JS | 'use client' on whole layouts |
| PPR | Static shell + dynamic holes | Too many holes |
| Islands | Mostly static + few widgets | Fully interactive apps |
Do
- Choose per route; push
'use client'/ islands down
Avoid
- One global rendering mode for every surface
Example: SSG docs + search island; streamed cart; CSR admin.
Detail: Rendering concepts
9. Micro-frontend integration patterns
What: How independently shipped UIs compose.
| Approach | Loads | Best when |
|---|---|---|
| Build-time packages | Compiled into shell | Few aligned teams |
| Runtime federation | Dynamic remotes | Independent deploy cadence |
| Server/edge fragments | HTML before hydration | SEO + fast LCP |
Do
- Thin shell; error boundaries; singleton shared deps
Avoid
- MFEs before modular boundaries exist; shared Redux across remotes
Example: Server hero + runtime checkout remote + build-time icons.
Detail: MFE concepts
10. Decision cheat-sheet
What: Fast picks when two patterns compete.
| If you need… | Prefer | Not |
|---|---|---|
| Testable business rules | Hexagonal use cases + ports | fetch inside components |
| Shared business language | DDD ubiquitous language + contexts | Framework jargon in domain APIs |
| Same word, different models | Bounded contexts + ACL | One shared god Product type |
| Squads shipping features | Vertical slices | One giant services/ |
| Shareable filters | URL state | Global store |
| Remote list/detail | TanStack Query | useEffect + useState |
| Complex screen flow | MVI / XState | Flag soup |
| SEO + personalization | SSR / streaming / RSC | CSR-only |
| Independent squad deploys | Runtime MFE | Giant monolith npm bump |
| Browser auth | BFF httpOnly session (or memory token) | Access token in localStorage |
| Push updates | SSE/WS → invalidate Query | Second source of truth in socket memory |
| Secrets + joins for UI | BFF | Browser → many microservices |
| Legacy rewrite | Strangler + ACL adapter | Big-bang cutover |
Do
- Write the constraint first, then pick the row
- Revisit after a milestone — promote patterns when pain is measured
Avoid
- Adopting every row on day one
Example: 8-person startup → Modules 1–6 patterns. Second squad + separate release train → hexagonal checkout module → later MFE.
Next
- Learning path
- Application Architecture Patterns — hexagonal & friends
- Domain-Driven Design — language, contexts, aggregates
- Module 1 · Components