If you've ever jumped into an older React codebase and felt like you were spelunking through a cave of confusion — you're not alone. Over the years, I've worked on everything from scrappy MVPs to production-scale apps, and one thing became very clear: project structure matters.
In this post, I’ll walk you through how I structure my React projects, why I’ve settled on this format, and how it scales cleanly over time.
🧠 The Core Philosophy
Before diving into the folder tree, here’s what drives my decisions:
- Separation of concerns over abstraction-for-the-sake-of-abstraction.
- Feature-first thinking — files should live where they belong conceptually.
- Scalability — what works for 3 components should still work for 300.
- Developer experience — fast onboarding, minimal context switching.
📁 The Folder Structure
Here’s the high-level layout I follow in almost every React project:
src/
│
├── assets/ # Images, fonts, global styles, etc.
├── components/ # Reusable UI components (atoms/molecules)
├── features/ # Feature-based modules (e.g. auth, dashboard)
├── hooks/ # Custom React hooks
├── lib/ # Utilities and shared logic (API clients, helpers)
├── pages/ # Route-based components (if using file-based routing)
├── layouts/ # Layout wrappers (MainLayout, AuthLayout, etc.)
├── store/ # Global state (Redux, Zustand, etc.)
├── types/ # TypeScript types and interfaces
└── App.tsx # Root component
🔍 A Deeper Dive
1. components/
Think of this as your design system zone.
-
Button.tsx,Modal.tsx,Input.tsxetc. - Small, dumb, reusable UI blocks.
- If it could appear on multiple pages, it probably belongs here.
2. features/
This is the heart of the app, organized by domain.
features/
├── auth/
│ ├── LoginForm.tsx
│ ├── authSlice.ts
│ ├── api.ts
│ └── hooks.ts
└── dashboard/
├── DashboardPage.tsx
├── widgets/
└── dashboardSlice.ts
Everything a feature needs (components, hooks, local state) stays encapsulated. It's modular, testable, and easy to maintain.
3. hooks/
Custom hooks like useDebounce, useFetch, useOnClickOutside.
- These are often used across features/components.
- Each one should be atomic and single-purpose.
4. lib/
A utility belt for the app — things like:
- Axios instances
- Date/time formatters
- Validation functions
- Anything not tied to the UI directly.
5. store/
For global state management (when necessary):
- Redux or Zustand store setup
- Middleware and persistence logic
- Keep feature-specific slices in the relevant
features/folder
6. types/
Centralized location for all shared types and interfaces.
If you're using TypeScript (you should!), this keeps your types from spreading like weeds across the project.
⚙️ Tools & Conventions I Use
-
Path aliases via
tsconfig.json(@components,@features, etc.) - Atomic Design thinking, loosely applied
- Prettier + ESLint + Husky for a clean, linted codebase
- Storybook for developing and documenting components
📈 Why This Works
- Easy onboarding: New devs can find what they need quickly.
- Scales gracefully: As the app grows, this structure won’t collapse.
- Flexible: It supports SSR (Next.js), SPAs, or Electron apps with minor tweaks.
- Testable: Each feature is isolated, which makes writing unit/integration tests easier.
✍️ Final Thoughts
There’s no one true way to structure React apps — but there are definitely better ways. The key is to stay consistent, think in terms of features and reusability, and always consider how your project might evolve.
Whether you're building a side hustle or a startup-scale application, a solid structure is the bedrock of clean, maintainable code.
How do you structure your React projects? Drop your tips or battle stories in the comments!

