ReactUnderstanding the Project Structure

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:

Bash
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:

Bash
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).

Bash
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
Note
The feature folder's `index.ts` is its public API. Other features import from `features/auth` (the index), never directly from `features/auth/store/authSlice.ts`. This enforces clear boundaries between features.
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.tsx

  • Hooks — camelCase with use prefix: useAuth.ts, usePagination.ts

  • Utilities — camelCase: formatDate.ts, validators.ts

  • Services — camelCase with Service or Api suffix: productService.ts, authApi.ts

  • Types — PascalCase interfaces: User, Product, CartItem. Type alias files use .types.ts suffix.

  • Index files — use index.ts (not index.tsx) in folders that re-export. Keep the barrel file short.

Component Folder Pattern

For complex components, use the folder-per-component pattern:

Bash
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 ProductCard

The index.ts allows importing as import ProductCard from '@components/ProductCard' instead of @components/ProductCard/ProductCard.

Start simple, refactor early
Do not over-engineer the structure before you know what features you need. Start with the flat small-project layout. When a folder grows past 5–7 files, split it. Premature structure adds complexity before it adds value. Feature folders usually emerge naturally around the third or fourth major feature.
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