Maintaining a scalable and easy-to-understand codebase starts with a solid project structure. When working with React + TypeScript, a well-thought-out folder and file organization can make collaboration smoother and development faster.

Let me show you how I usually organize my projects and the reasoning behind each decision.


πŸ“ Base folder structure

Here's a basic overview of how I structure most of my React + TypeScript apps:

src/
β”‚
β”œβ”€β”€ assets/         # Static files (images, icons, fonts)
β”œβ”€β”€ components/     # Reusable UI components
β”œβ”€β”€ features/       # Feature-based folders (each one contains logic + UI)
β”œβ”€β”€ hooks/          # Custom hooks
β”œβ”€β”€ pages/          # Route-level components (if using React Router or Next.js)
β”œβ”€β”€ services/       # API calls and business logic
β”œβ”€β”€ types/          # Global TypeScript types and interfaces
β”œβ”€β”€ utils/          # Utility functions and helpers
β”œβ”€β”€ constants/      # App-wide constants (enums, config, etc.)
β”œβ”€β”€ store/          # Redux, Zustand or any state management logic
└── index.tsx       # App entry point


🧩 Features folder

Instead of a strict separation by type (e.g., all components in one place), I prefer a feature-based approach:

features/
└── auth/
    β”œβ”€β”€ components/
    β”œβ”€β”€ hooks/
    β”œβ”€β”€ services/
    β”œβ”€β”€ AuthPage.tsx
    └── authSlice.ts

This way, everything related to the auth feature is grouped together. It improves discoverability and reduces coupling between unrelated parts of the app.


πŸ“¦ Components vs features

Use components/ for truly reusable and dumb components (e.g., Button, Modal, Input). These should not depend on any app-specific logic.

Use features/ for domain-specific components that are tied to business logic.


πŸ› οΈ Services

Place your API calls, external integrations, or domain logic here:

// services/userService.ts
export const getUserById = async (id: string) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};

Group them by domain when needed (e.g., authService, userService, reportService).


✍️ Types and interfaces

Define global types in types/ and feature-specific ones inside the relevant feature folder:

// types/user.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

Or:

// features/reports/types.ts
export interface Report {
  id: string;
  createdAt: string;
  data: ReportData;
}


βš™οΈ State management (OPTIONAL)

If you're using a state management library like Redux or Zustand, place slices/stores under store/:

store/
β”œβ”€β”€ index.ts         # Combine reducers or stores
└── userSlice.ts

Or colocate slices within the features/ folder if they’re tightly scoped to that feature.


πŸ’‘ Bonus tips

  • βœ… Use barrel files (index.ts) to simplify imports.
  • βœ… Name files using PascalCase for components (LoginPage.tsx) and camelCase for utils (formatDate.ts).
  • βœ… Keep styles close to the component (Component.module.css or styled-components).
  • βœ… Prefer co-location: keep files that change together near each other.

βœ… Summary

FolderPurpose
components/Reusable UI components
features/Domain-specific logic and components
hooks/Custom hooks
pages/Route components
services/API and business logic
types/TypeScript types and interfaces
store/State management logic
utils/Utility functions
assets/Static files

πŸ“Œ Conclusion

There’s no β€œperfect” folder structure, but choosing one that prioritizes clarity, scalability, and collaboration makes a huge difference. Feature-based organization helps keep related code together, and TypeScript makes it easier to maintain consistency across your project.