Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A16
49 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A16 · Part 3 · Advanced

Architecture at scale

Organising by feature, drawing boundaries, and the decisions that are hard to reverse.

4 min read8 sections2 exercises
49 of 57

In this chapter

  • Organise by feature, not by kind
  • The boundary rules that stop it decaying
  • The dependency direction
  • Monorepos
  • Micro-frontends: usually the wrong answer

and 3 more

about 4 min
08On this page
  1. Organise by feature, not by kind
  2. The boundary rules that stop it decaying
  3. The dependency direction
  4. Monorepos
  5. Micro-frontends: usually the wrong answer
  6. Decisions that are expensive to reverse
  7. Write the reasons down
  8. Guardrails over discipline

Organise by feature, not by kind

// ✗ every change touches five folders
components/  hooks/  utils/  types/  api/

// ✓ a feature is one folder, deletable in one move
src/
  features/
    checkout/
      components/  hooks/  api.ts  types.ts  index.ts
    catalogue/
  shared/
    ui/  hooks/  lib/
  app/

Grouping by kind means a feature is scattered across the tree and nobody can tell what belongs to what. Grouping by feature means the answer to "can I delete this?" is visible, and a new person can be pointed at one folder.

The boundary rules that stop it decaying

  • A feature may import from shared/. It must not import from another feature's internals.
  • If two features need the same thing, it moves to shared/ — a deliberate promotion, not a shortcut import.
  • Each feature exposes a small index.ts. Everything else is private.
// eslint-plugin-boundaries or an import/no-restricted-paths rule
"no-restricted-imports": ["error", {
  patterns: ["@/features/*/!(index)"]     // only the public entry
}]

Conventions decay; lint rules do not. One rule that fails the build is worth more than a paragraph in a README nobody reads twice.

Layers, not just folders

UI renders and handles interaction. Domain holds the rules — pricing, permissions, validation — as plain functions with no React in them. Data talks to the network. The value of separating them is that the domain becomes testable without rendering anything, and swapping REST for GraphQL touches one layer.

The dependency direction

app  →  features  →  shared        // ✓ one direction, always

shared/ must never import from features/. The moment it does, everything depends on everything, imports go circular, and the build graph stops meaning anything. This is the rule worth enforcing above all others.

Monorepos

Worth it whenNot worth it when
Several apps share a component libraryOne app
Types are shared between frontend and backendNothing is shared
An atomic change must cross packagesTeams release independently on purpose

The cost is real: CI gets slower without task caching, tooling gets more complicated, and versioning between internal packages becomes a decision somebody has to own. A workspace with three packages is fine; adopt Turborepo or Nx when the CI time makes you.

Micro-frontends: usually the wrong answer

Splitting a frontend into independently deployed applications solves an organisational problem — several teams that cannot coordinate releases. It costs you a shared runtime, duplicated dependencies, cross-app routing and state, and a debugging story that spans repositories.

If you have one team, you do not have the problem it solves. Module federation is not a way to make a large codebase feel smaller.

Decisions that are expensive to reverse

DecisionWhy it is hard to undo
Framework and rendering modelTouches every file and every deployment assumption
Styling approachEvery component; and runtime CSS-in-JS blocks Server Components
State modelMigrating a store threads through every consumer
Folder structure and boundariesEvery import; and imports encode assumptions
The data layer's shapeEvery component that fetches

Spend real time on these five. Everything else — which date library, which icon set, whether a component takes three props or four — is cheap to change and not worth a meeting.

Write the reasons down

docs/decisions/
  0003-server-components-for-the-catalogue.md

One short file per significant decision: the context, the options, the choice, and the trade accepted. Six months later, when someone asks why the app is not using the obvious thing, the answer exists — and if the context has changed, the decision can be revisited on purpose rather than drifted away from.

Guardrails over discipline

  • Lint the import boundaries so a violation fails CI.
  • Budget the bundle in CI, so a regression is caught the day it lands.
  • Enforce the type checker on every push, with strict on.
  • Codeowners on shared/, because that is where accidental coupling enters.

Every one of these replaces a rule people are expected to remember with one a machine checks.

Interview answer, one sentence

"Organise by feature so a feature is one deletable folder, keep the dependency direction one-way — app to features to shared, never back — and enforce that with a lint rule rather than a convention. Then spend the real thinking on the five decisions that are expensive to reverse, and write down why each was made."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsLint the dependency direction by handadvanced
  • 7 testsIs a monorepo worth it here?advanced
All 54 for this level →
A16

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. A15Previous · A15Advanced TypeScript with React
  2. A16you are here
  3. Next · A17Custom renderersReact is not a DOM library. The DOM is one target among several.3 min read

01On this page

8 sections · 0%

  1. 01Organise by feature, not by kind
  2. 02The boundary rules that stop it decaying
  3. 03The dependency direction
  4. 04Monorepos
  5. 05Micro-frontends: usually the wrong answer
  6. 06Decisions that are expensive to reverse
  7. 07Write the reasons down
  8. 08Guardrails over discipline

[] chapters · t top