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
Folder | Purpose |
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.