12 KiB
File Organization
Proper file and directory structure for maintainable, scalable frontend code in the the application.
features/ vs components/ Distinction
features/ Directory
Purpose: Domain-specific features with their own logic, API, and components
When to use:
- Feature has multiple related components
- Feature has its own API endpoints
- Feature has domain-specific logic
- Feature has custom hooks/utilities
Examples:
features/posts/- Project catalog/post managementfeatures/blogs/- Blog builder and renderingfeatures/auth/- Authentication flows
Structure:
features/
my-feature/
api/
myFeatureApi.ts # API service layer
components/
MyFeatureMain.tsx # Main component
SubComponents/ # Related components
hooks/
useMyFeature.ts # Custom hooks
useSuspenseMyFeature.ts # Suspense hooks
helpers/
myFeatureHelpers.ts # Utility functions
types/
index.ts # TypeScript types
index.ts # Public exports
components/ Directory
Purpose: Truly reusable components used across multiple features
When to use:
- Component is used in 3+ places
- Component is generic (no feature-specific logic)
- Component is a UI primitive or pattern
Examples:
components/SuspenseLoader/- Loading wrappercomponents/CustomAppBar/- Application headercomponents/ErrorBoundary/- Error handlingcomponents/LoadingOverlay/- Loading overlay
Structure:
components/
SuspenseLoader/
SuspenseLoader.tsx
SuspenseLoader.test.tsx
CustomAppBar/
CustomAppBar.tsx
CustomAppBar.test.tsx
Feature Directory Structure (Detailed)
Complete Feature Example
Based on features/posts/ structure:
features/
posts/
api/
postApi.ts # API service layer (GET, POST, PUT, DELETE)
components/
PostTable.tsx # Main container component
grids/
PostDataGrid/
PostDataGrid.tsx
drawers/
ProjectPostDrawer/
ProjectPostDrawer.tsx
cells/
editors/
TextEditCell.tsx
renderers/
DateCell.tsx
toolbar/
CustomToolbar.tsx
hooks/
usePostQueries.ts # Regular queries
useSuspensePost.ts # Suspense queries
usePostMutations.ts # Mutations
useGridLayout.ts # Feature-specific hooks
helpers/
postHelpers.ts # Utility functions
validation.ts # Validation logic
types/
index.ts # TypeScript types/interfaces
queries/
postQueries.ts # Query key factories (optional)
context/
PostContext.tsx # React context (if needed)
index.ts # Public API exports
Subdirectory Guidelines
api/ Directory
Purpose: Centralized API calls for the feature
Files:
{feature}Api.ts- Main API service
Pattern:
// features/my-feature/api/myFeatureApi.ts
import apiClient from '@/lib/apiClient';
export const myFeatureApi = {
getItem: async (id: number) => {
const { data } = await apiClient.get(`/blog/items/${id}`);
return data;
},
createItem: async (payload) => {
const { data } = await apiClient.post('/blog/items', payload);
return data;
},
};
components/ Directory
Purpose: Feature-specific components
Organization:
- Flat structure if <5 components
- Subdirectories by responsibility if >5 components
Examples:
components/
MyFeatureMain.tsx # Main component
MyFeatureHeader.tsx # Supporting components
MyFeatureFooter.tsx
# OR with subdirectories:
containers/
MyFeatureContainer.tsx
presentational/
MyFeatureDisplay.tsx
blogs/
MyFeatureBlog.tsx
hooks/ Directory
Purpose: Custom hooks for the feature
Naming:
useprefix (camelCase)- Descriptive of what they do
Examples:
hooks/
useMyFeature.ts # Main hook
useSuspenseMyFeature.ts # Suspense version
useMyFeatureMutations.ts # Mutations
useMyFeatureFilters.ts # Filters/search
helpers/ Directory
Purpose: Utility functions specific to the feature
Examples:
helpers/
myFeatureHelpers.ts # General utilities
validation.ts # Validation logic
transblogers.ts # Data transblogations
constants.ts # Constants
types/ Directory
Purpose: TypeScript types and interfaces
Files:
types/
index.ts # Main types, exported
internal.ts # Internal types (not exported)
Import Aliases (Vite Configuration)
Available Aliases
From vite.config.ts lines 180-185:
| Alias | Resolves To | Use For |
|---|---|---|
@/ |
src/ |
Absolute imports from src root |
~types |
src/types |
Shared TypeScript types |
~components |
src/components |
Reusable components |
~features |
src/features |
Feature imports |
Usage Examples
// ✅ PREFERRED - Use aliases for absolute imports
import { apiClient } from '@/lib/apiClient';
import { SuspenseLoader } from '~components/SuspenseLoader';
import { postApi } from '~features/posts/api/postApi';
import type { User } from '~types/user';
// ❌ AVOID - Relative paths from deep nesting
import { apiClient } from '../../../lib/apiClient';
import { SuspenseLoader } from '../../../components/SuspenseLoader';
When to Use Which Alias
@/ (General):
- Lib utilities:
@/lib/apiClient - Hooks:
@/hooks/useAuth - Config:
@/config/theme - Shared services:
@/services/authService
~types (Type Imports):
import type { Post } from '~types/post';
import type { User, UserRole } from '~types/user';
~components (Reusable Components):
import { SuspenseLoader } from '~components/SuspenseLoader';
import { CustomAppBar } from '~components/CustomAppBar';
import { ErrorBoundary } from '~components/ErrorBoundary';
~features (Feature Imports):
import { postApi } from '~features/posts/api/postApi';
import { useAuth } from '~features/auth/hooks/useAuth';
File Naming Conventions
Components
Pattern: PascalCase with .tsx extension
MyComponent.tsx
PostDataGrid.tsx
CustomAppBar.tsx
Avoid:
- camelCase:
myComponent.tsx❌ - kebab-case:
my-component.tsx❌ - All caps:
MYCOMPONENT.tsx❌
Hooks
Pattern: camelCase with use prefix, .ts extension
useMyFeature.ts
useSuspensePost.ts
useAuth.ts
useGridLayout.ts
API Services
Pattern: camelCase with Api suffix, .ts extension
myFeatureApi.ts
postApi.ts
userApi.ts
Helpers/Utilities
Pattern: camelCase with descriptive name, .ts extension
myFeatureHelpers.ts
validation.ts
transblogers.ts
constants.ts
Types
Pattern: camelCase, index.ts or descriptive name
types/index.ts
types/post.ts
types/user.ts
When to Create a New Feature
Create New Feature When:
- Multiple related components (>3)
- Has own API endpoints
- Domain-specific logic
- Will grow over time
- Reused across multiple routes
Example: features/posts/
- 20+ components
- Own API service
- Complex state management
- Used in multiple routes
Add to Existing Feature When:
- Related to existing feature
- Shares same API
- Logically grouped
- Extends existing functionality
Example: Adding export dialog to posts feature
Create Reusable Component When:
- Used across 3+ features
- Generic, no domain logic
- Pure presentation
- Shared pattern
Example: components/SuspenseLoader/
Import Organization
Import Order (Recommended)
// 1. React and React-related
import React, { useState, useCallback, useMemo } from 'react';
import { lazy } from 'react';
// 2. Third-party libraries (alphabetical)
import { Box, Paper, Button, Grid } from '@mui/material';
import type { SxProps, Theme } from '@mui/material';
import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
import { createFileRoute } from '@tanstack/react-router';
// 3. Alias imports (@ first, then ~)
import { apiClient } from '@/lib/apiClient';
import { useAuth } from '@/hooks/useAuth';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
import { SuspenseLoader } from '~components/SuspenseLoader';
import { postApi } from '~features/posts/api/postApi';
// 4. Type imports (grouped)
import type { Post } from '~types/post';
import type { User } from '~types/user';
// 5. Relative imports (same feature)
import { MySubComponent } from './MySubComponent';
import { useMyFeature } from '../hooks/useMyFeature';
import { myFeatureHelpers } from '../helpers/myFeatureHelpers';
Use single quotes for all imports (project standard)
Public API Pattern
feature/index.ts
Export public API from feature for clean imports:
// features/my-feature/index.ts
// Export main components
export { MyFeatureMain } from './components/MyFeatureMain';
export { MyFeatureHeader } from './components/MyFeatureHeader';
// Export hooks
export { useMyFeature } from './hooks/useMyFeature';
export { useSuspenseMyFeature } from './hooks/useSuspenseMyFeature';
// Export API
export { myFeatureApi } from './api/myFeatureApi';
// Export types
export type { MyFeatureData, MyFeatureConfig } from './types';
Usage:
// ✅ Clean import from feature index
import { MyFeatureMain, useMyFeature } from '~features/my-feature';
// ❌ Avoid deep imports (but OK if needed)
import { MyFeatureMain } from '~features/my-feature/components/MyFeatureMain';
Directory Structure Visualization
src/
├── features/ # Domain-specific features
│ ├── posts/
│ │ ├── api/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── helpers/
│ │ ├── types/
│ │ └── index.ts
│ ├── blogs/
│ └── auth/
│
├── components/ # Reusable components
│ ├── SuspenseLoader/
│ ├── CustomAppBar/
│ ├── ErrorBoundary/
│ └── LoadingOverlay/
│
├── routes/ # TanStack Router routes
│ ├── __root.tsx
│ ├── index.tsx
│ ├── project-catalog/
│ │ ├── index.tsx
│ │ └── create/
│ └── blogs/
│
├── hooks/ # Shared hooks
│ ├── useAuth.ts
│ ├── useMuiSnackbar.ts
│ └── useDebounce.ts
│
├── lib/ # Shared utilities
│ ├── apiClient.ts
│ └── utils.ts
│
├── types/ # Shared TypeScript types
│ ├── user.ts
│ ├── post.ts
│ └── common.ts
│
├── config/ # Configuration
│ └── theme.ts
│
└── App.tsx # Root component
Summary
Key Principles:
- features/ for domain-specific code
- components/ for truly reusable UI
- Use subdirectories: api/, components/, hooks/, helpers/, types/
- Import aliases for clean imports (@/, ~types, ~components, ~features)
- Consistent naming: PascalCase components, camelCase utilities
- Export public API from feature index.ts
See Also:
- component-patterns.md - Component structure
- data-fetching.md - API service patterns
- complete-examples.md - Full feature example