How to architect a complex CRM front-end
This is my experience after 4 years working on a complex CRM at a large company, plus freelance work, personal projects, and SaaS development. Most of my experience is in React, so let’s focus on that.
Remember: every system is different. Adapt what makes sense for your context — create subfolders where you need them, ignore what doesn’t apply.
The structure
src/
├── app.tsx
├── pages/
├── components/
│ ├── core/
│ └── shared/
├── hooks/
│ ├── core/
│ └── shared/
├── api/
├── models/
└── utils/
app.tsx
The application’s entry point. This is where the global providers live: theme, language, authentication, routes, granular permissions, and so on.
components/ and hooks/
Both follow the same separation logic:
| Folder | What goes here |
|---|---|
core/ | No business rules — used in any context |
shared/ | With business rules — shareable across screens |
Why separate core from shared? Core tends to be used dozens of times more often than shared. Separating them makes maintenance and organization easier.
Doesn’t authentication have business rules?
Authentication is a feature used on every page — the entire company follows the same logic. When a rule is global and doesn’t vary by page or area, it can be treated as
core.But there are certainly exceptions. There’s no one-size-fits-all recipe — think carefully about the type of application you’re building. And remember the balance between KISS, YAGNI and DRY.
pages/
Each folder within pages/ represents a page, not a feature. Components, hooks, and providers specific to a page belong inside that page’s folder — not in components/ or hooks/.
What if I need to share a page?
Then it’s worth creating a component in
shared/. If the page is too large, split it into components and move them toshared/.
api/
The communication layer with the backend. It’s worth using a query library like React Query or RTK Query. Each file represents an API or service, organized in whatever way makes the most sense given the backend.
Business rules don’t usually belong here.
models/
The application’s types and models. The organization depends on how you structured api/ and pages/ — and that varies quite a bit from project to project.
utils/
Utility functions, with or without business rules. Usually there’s no need to separate them — but do so if necessary (KISS & YAGNI).
Dependency rules
Avoid circular dependencies as much as possible. Implement linting to enforce this, if possible.
core → does not import from shared
core → imports from api only in very specific cases
(e.g.: feature flags, authentication)
shared → imports from core
pages → imports from anywhere, and does not export anywhere
api → imports from models
models → does NOT import from api
utils without business rules → can be imported from anywhere
utils with business rules → anywhere, except core