A messy codebase slows you down. A good structure doesn’t guarantee a great app, but it makes everything easier: onboarding, debugging, testing, and scaling. Here's how I structure my React apps after years of building real-world projects.
The basics
I start with a flat foundation and scale only when needed. No premature abstraction.
app/ – entry points and routing
If I use Next.js or Remix, this is the file-based routing system. Otherwise, I keep top-level pages here and treat them as composition roots, they pull in layouts and feature components.
components/ – dumb UI
These are reusable, stateless components. Think:
ButtonCardInputDialog
They handle display only, no app logic. Styled with Tailwind or any atomic CSS utility.
features/ – self-contained modules
Each feature lives in its own folder:
Each folder handles its own logic, UI, and API interaction. This scales better than dumping everything into /components.
lib/ – helpers, hooks, utils
Small pieces used across the app:
lib/fetcher.tslib/useMediaQuery.tslib/format.ts
Keep them pure, testable, and decoupled from app state.
types/ – app-wide types
All shared types go here. I avoid polluting global types unless necessary.
styles/ – themes or CSS vars
Mostly used for Tailwind configs, tokens, or global styles. Sometimes I colocate scoped styles, but this holds the system-level stuff.
Bonus: config/, hooks/, context/
Only if needed. I don’t create these folders by default. Once a pattern emerges (like 5+ hooks in lib), I extract.
Conclusion
There’s no one right way, but structure reflects how you think. Keep it simple, modular, and scalable. Optimize for clarity. Refactor when needed. Codebases are living systems, not blueprints.