
Combining Clean Architecture + Feature-Based in React — does it really fix the earlier trade-offs, or am I missing new pitfalls?
Hi everyone. I compared four ways to structure a React project by rebuilding the same app (posts CRUD against an open API) in each one. The last pattern combines Clean Architecture with Feature-Based, and I'd really appreciate a sanity check from more experienced devs.
Here's the progression I went through, and the problem I felt at each step:
- Feature-Based (colocate everything for a feature in one folder): great for navigation and deletion, but nothing controls how features depend on each other (circular deps creep in),
shared/turns into a junk drawer, and there's no notion of layers. (Feature-Based write-up) - FSD (Feature-Sliced Design): fixes that with standardized layers + a one-way import rule, so circular deps become structurally impossible. But the business logic still lives inside React/TanStack Query — the entity's
apilayer imports axios and react-query directly. (FSD write-up) - Clean Architecture: pulls business logic out of the framework with the Dependency Rule (dependencies point only inward; the domain knows nothing about React or axios). Great for testing and reuse — but now the code for "one feature" is scattered across
domain/,infrastructure/,presentation/. Which is ironically the same "scattered by type" problem Feature-Based tried to solve. (Clean Architecture write-up) - The combination: keep the Dependency Rule (domain is pure TS, infrastructure holds the adapters), but colocate the UI (hooks + components) by feature in
features/{feature}/. "Clean inside, Feature outside."
Rough shape:
src/
domain/{domain}/ # pure TS: entities, rules, use cases (no framework imports)
infrastructure/ # adapters: repository impls, query keys, stores
features/{feature}/ # hooks + components, colocated
pages/ , router/ # composition only
shared/ , providers/
A few extra decisions I made: split the repository interface into Commands/Queries (CQS), write a UseCase only when there's real logic (plain CRUD calls the repository directly), and lean on React Compiler so there's no manual useMemo/useCallback.
What I'd love feedback on:
- Does this combination actually solve the earlier patterns' problems, or does it just move them around? Is "Clean inside + Feature outside" a real improvement over plain FSD or plain Clean, or is it over-engineering in disguise?
- What problems does this pattern itself have that I might not see yet? Boilerplate, the domain <-> infrastructure indirection, the "is this a UseCase or a direct repo call?" judgment, testing overhead, onboarding cost — where does it bite in real projects?
Honest criticism is very welcome. I'd rather hear "this is overkill for most apps" now than after I build on it.
Full write-up (with all the code) on Medium (Free): https://medium.com/@inkweonkim/react-architecture-combining-clean-architecture-feature-based-92cf7ba226fe
(English isn't my first language, so I apologize in advance for any awkward phrasing — happy to clarify anything that reads strangely.)