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.tsx
etc. - 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!
Top comments (0)