Understanding the Project Structure
React has no enforced folder structure. Vite gives you src/App.jsx and src/main.jsx — everything else is up to you. This freedom is powerful and paralyzing in equal measure. Without a clear structure, files pile up, imports get unwieldy, and onboarding new developers takes longer than it should.
This page covers three levels of structure: a small project that keeps things simple, a medium project with clear separation of concerns, and a large project using a feature-based (also called domain-based) layout that scales to dozens of developers.
Small Project (under ~10 components)
For a personal project, portfolio site, or prototype, keep it flat:
src/ ├── components/ # all reusable components │ ├── Button.jsx │ ├── Navbar.jsx │ └── Footer.jsx ├── pages/ # one component per route │ ├── Home.jsx │ ├── About.jsx │ └── Contact.jsx ├── App.jsx ├── main.jsx └── index.css
No subdirectories within components/ or pages/. All components in one place. When the project is small, the cognitive overhead of nested folders is greater than the benefit.
Medium Project (10–50 components)
As the project grows, introduce subdirectories by concern:
src/ ├── components/ # reusable, general-purpose UI components │ ├── ui/ # primitive building blocks (Button, Input, Modal) │ │ ├── Button/ │ │ │ ├── Button.jsx │ │ │ ├── Button.module.css │ │ │ └── index.js # re-exports Button as default │ │ └── Input/ │ └── layout/ # structural components (Header, Sidebar, Footer) │ ├── Header.jsx │ └── Footer.jsx ├── pages/ # route-level components (one per page/view) │ ├── HomePage.jsx │ ├── ProductsPage.jsx │ └── ProductDetailPage.jsx ├── hooks/ # custom hooks (useWindowSize, useLocalStorage) │ ├── useWindowSize.js │ └── useLocalStorage.js ├── utils/ # pure utility functions (formatDate, slugify) │ ├── formatDate.js │ └── validators.js ├── services/ # API calls and external integrations │ ├── api.js # axios/fetch instance, base config │ ├── productService.js │ └── authService.js ├── store/ # global state (Redux slices, Zustand stores) │ ├── authSlice.js │ └── cartSlice.js ├── types/ # TypeScript types and interfaces (if using TS) │ ├── product.ts │ └── user.ts ├── App.jsx └── main.jsx
Large Project: Feature-Based Structure
For applications with multiple distinct features — auth, dashboard, products, checkout, user profile — a feature-based (or domain-based) structure keeps related code co-located. Instead of splitting by technical type (all hooks together, all components together), you split by business domain (everything for products together).
src/ ├── features/ │ ├── auth/ │ │ ├── components/ # LoginForm, SignupForm, PasswordReset │ │ │ ├── LoginForm.tsx │ │ │ └── SignupForm.tsx │ │ ├── hooks/ # useAuth, useLoginForm │ │ │ └── useAuth.ts │ │ ├── services/ # authApi.ts (login, logout, refresh) │ │ │ └── authApi.ts │ │ ├── store/ # authSlice.ts │ │ │ └── authSlice.ts │ │ ├── types/ # User, AuthState interfaces │ │ │ └── auth.types.ts │ │ └── index.ts # public API — what this feature exports │ │ │ ├── products/ │ │ ├── components/ │ │ │ ├── ProductCard.tsx │ │ │ ├── ProductGrid.tsx │ │ │ └── ProductDetail.tsx │ │ ├── hooks/ │ │ │ └── useProducts.ts │ │ ├── services/ │ │ │ └── productsApi.ts │ │ └── index.ts │ │ │ └── cart/ │ ├── components/ │ ├── hooks/ │ ├── store/ │ └── index.ts │ ├── components/ # shared components used by multiple features │ ├── ui/ # Button, Input, Modal, Spinner, Badge │ └── layout/ # AppShell, Header, Sidebar, Footer │ ├── hooks/ # shared hooks (not feature-specific) │ ├── useWindowSize.ts │ └── useDebounce.ts │ ├── utils/ # shared utilities │ └── formatters.ts │ ├── services/ # shared API config (axios instance, interceptors) │ └── httpClient.ts │ ├── pages/ # thin route components that compose features │ ├── HomePage.tsx │ ├── ProductsPage.tsx │ └── CheckoutPage.tsx │ ├── router/ # route definitions │ └── index.tsx │ ├── store/ # root Redux store / global state setup │ └── index.ts │ ├── types/ # global shared types │ └── common.types.ts │ ├── App.tsx └── main.tsx
The Folder Glossary
components/ — UI components. Subdivide into
ui/(atoms: Button, Input),layout/(structural: Header, Sidebar), and feature-specific.pages/ (or views/) — one component per application route. These are thin — they compose feature components, pass data, and don't contain business logic themselves.
hooks/ — custom React hooks. Name them
use<Something>.ts. Extract logic from components here.utils/ — pure functions with no side effects and no React imports.
formatDate,slugify,calculateTotal. These should be easily unit-testable.services/ (or api/) — API calls and external service integrations. Abstracts fetch/axios calls behind named functions so components don't contain raw URLs.
store/ — global state configuration. Redux slices, Zustand stores, Jotai atoms.
types/ — TypeScript interfaces, enums, and type aliases. Shared types live here; feature-specific types can live inside the feature folder.
Naming Conventions
Components — PascalCase:
UserProfile.tsx,ProductCard.tsxHooks — camelCase with
useprefix:useAuth.ts,usePagination.tsUtilities — camelCase:
formatDate.ts,validators.tsServices — camelCase with
ServiceorApisuffix:productService.ts,authApi.tsTypes — PascalCase interfaces:
User,Product,CartItem. Type alias files use.types.tssuffix.Index files — use
index.ts(notindex.tsx) in folders that re-export. Keep the barrel file short.
Component Folder Pattern
For complex components, use the folder-per-component pattern:
components/
└── ProductCard/
├── ProductCard.tsx # the component implementation
├── ProductCard.module.css # scoped styles
├── ProductCard.test.tsx # unit tests alongside the component
├── ProductCard.stories.tsx # Storybook story (if used)
└── index.ts # export default ProductCardThe index.ts allows importing as import ProductCard from '@components/ProductCard' instead of @components/ProductCard/ProductCard.
What Not to Do
Avoid deeply nested folders — three levels deep is usually the maximum before imports become hard to reason about
Avoid mixing concerns in one file — a 600-line component file with its own hooks, utilities, and types is a clear signal to refactor
Avoid barrel-file overuse — re-exporting every file in every folder through index files sounds clean but causes slow TypeScript compilation and circular dependency nightmares in large codebases
Avoid renaming 'pages' to 'screens' or 'views' inconsistently — pick one term and use it everywhere