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:
Button
Card
Input
Dialog
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.ts
lib/useMediaQuery.ts
lib/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.