As React applications grow from dozens to hundreds of components, organizing files by technical type (e.g., placing all components in one /components folder and all hooks in /hooks) breaks down quickly. The solution adopted by high-performing teams is **Feature-Driven Architecture**.
1. The Feature-Driven Directory Layout
Instead of grouping by role, group files by business domain (e.g. auth, billing, dashboard, products). Shared primitives live in top-level directories.
Project Tree Layout
src/
├── app/ # Route definitions & page wrappers (Next.js App Router)
├── components/ # Global shared UI components (Button, Modal, Input)
├── features/ # Business domain modules
│ ├── auth/
│ │ ├── api/ # useLoginQuery.ts, useRegisterMutation.ts
│ │ ├── components/ # LoginForm.tsx, OAuthButtons.tsx
│ │ ├── hooks/ # useAuthSession.ts
│ │ └── types/ # index.ts
│ └── products/
│ ├── api/ # useProducts.ts
│ ├── components/ # ProductGrid.tsx, ProductCard.tsx
│ └── types/ # product.ts
├── hooks/ # Global shared utility hooks (useDebounce, useMediaQuery)
├── lib/ # Third-party client setups (axios, supabase, stripe)
└── types/ # Global shared TypeScript definitions
Colocation Benefit: When working on the
products feature, everything related (the API call, component UI, and TypeScript interfaces) is in one folder. Deleting or refactoring a feature requires removing only that folder.
2. Clean API Layer with TanStack Query
Decouple your HTTP calls and caching from the rendering components by wrapping query calls inside custom feature hooks.
features/products/api/useProducts.ts
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/lib/api-client';
import type { Product } from '../types';
export const useProducts = (category?: string) => {
return useQuery({
queryKey: ['products', category],
queryFn: async (): Promise<Product[]> => {
const { data } = await apiClient.get('/api/v1/products', {
params: { category }
});
return data;
},
staleTime: 1000 * 60 * 5 // Cache fresh for 5 minutes
});
};
3. Summary Checklist
- Keep global
components/uistrictly presentational and reusable. - Place business-heavy components inside their respective
features/[name]/folders. - Export public feature components via
features/[name]/index.tsindex files. - Use absolute imports with TypeScript path aliases (
@/*) to avoid messy relative paths like../../../../.