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/ui strictly presentational and reusable.
  • Place business-heavy components inside their respective features/[name]/ folders.
  • Export public feature components via features/[name]/index.ts index files.
  • Use absolute imports with TypeScript path aliases (@/*) to avoid messy relative paths like ../../../../.
← Previous Guide C# Concepts Every Developer Should Know Next Guide → Docker for Developers