What you'll learn
- Prefer feature folders over type-only folders at scale
- Keep UI, state, services, and converters separable inside a feature
- Enforce dependency direction with lint rules
- Migrate one feature at a time
Direct answer: organize by feature (billing, auth, settings), not by file type (components/, hooks/, utils/ only). Inside each feature, keep clear boundaries so UI does not own HTTP and API field names. Enforce import direction with tooling.
For the boundary model, see Maintainable React Architecture. For naming and lint layers, see Project Standards.
When to use this
- Multiple teams touch the same React app
- Features are hard to find because files are sorted by type
- Imports form cycles between “shared” utilities and features
When not to use this
- A tiny app with a few routes: a simple
pages+componentssplit is fine - You are mid-incident: do not reorganize while fixing a production outage
Practical shape
src/
app/ # routes / shell
features/
billing/
ui/
state/
api/
model/ # converters / domain types
index.ts # public exports only
settings/
...
shared/
ui/
lib/Other features import from features/billing via index.ts, not from deep private files.
Comparison
| Approach | Works when | Breaks when |
|---|---|---|
| Type folders only | Small codebase | Features span many folders |
| Feature folders | Team-scale product | Public API of each feature is fuzzy |
| Monorepo packages | Multiple apps/libs | Process cost without clear package boundaries |
Common mistakes
shared/becoming a junk drawer- Cross-feature imports that skip the feature public API
- Rewriting the whole tree in one PR
Checklist
- Can a new engineer find “billing” in one folder?
- Does UI import API types directly, or through a converter?
- Are deep imports banned or linted?
- Is migration feature-by-feature?
Related
Final recommendation
At team scale, feature folders plus thin public exports beat type-only trees. Keep boundaries inside the feature, lint the edges, and migrate one vertical at a time.