| name | project-structure |
| description | Use when creating new features, files, or folders. Defines the feature-driven directory layout, unidirectional import rules, and file placement decisions. |
Project Structure
When to use this skill
Use this skill when you need to:
- Create a new feature module
- Add a new file or folder to the project
- Decide where a piece of code should live
- Understand the import rules and dependency flow
Overview
This skill defines how the codebase is organized. Every file and folder must be placed according to these rules. The architecture is feature-driven โ most code lives inside src/features/, while truly shared code lives in top-level shared directories.
Top-Level src/ Structure
src/
โโโ app/ # Application shell โ routes, providers, router config
โโโ assets/ # Global static files (images, fonts, SVGs)
โโโ components/ # Shared components used across multiple features
โโโ config/ # Global configuration (env vars, path definitions)
โโโ features/ # Feature modules โ the core of the application
โโโ hooks/ # Shared custom hooks
โโโ lib/ # Pre-configured library wrappers (API client, auth, react-query)
โโโ stores/ # Global state stores (Zustand)
โโโ testing/ # Test utilities, mocks, MSW handlers, test db
โโโ types/ # Shared TypeScript type definitions
โโโ utils/ # Shared utility functions
The app/ Directory
The app/ directory is the application layer. It is responsible for:
- Route definitions โ All routes are defined in
app/router.tsx using lazy imports for code splitting
- Global providers โ
app/provider.tsx wraps the app with all necessary providers (QueryClient, ErrorBoundary, AuthLoader, etc.)
- Route components โ Each page lives in
app/routes/ organized by path structure
app/
โโโ routes/
โ โโโ landing.tsx
โ โโโ not-found.tsx
โ โโโ auth/
โ โ โโโ login.tsx
โ โ โโโ register.tsx
โ โโโ app/
โ โโโ root.tsx
โ โโโ dashboard.tsx
โ โโโ profile.tsx
โ โโโ users.tsx
โ โโโ discussions/
โ โโโ discussions.tsx
โ โโโ discussion.tsx
โโโ provider.tsx
โโโ router.tsx
Provider Pattern
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = React.useState(() => new QueryClient({ defaultOptions: queryConfig }));
return (
<React.Suspense fallback={<LoadingSpinner />}>
<ErrorBoundary FallbackComponent={MainErrorFallback}>
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<Notifications />
<AuthLoader renderLoading={() => <LoadingSpinner />}>
{children}
</AuthLoader>
</QueryClientProvider>
</HelmetProvider>
</ErrorBoundary>
</React.Suspense>
);
};
Router Pattern (Code Splitting)
export const createAppRouter = (queryClient: QueryClient) =>
createBrowserRouter([
{
path: paths.home.path,
lazy: () => import('./routes/landing').then(convert(queryClient)),
},
{
path: paths.app.root.path,
element: <ProtectedRoute><AppRoot /></ProtectedRoute>,
ErrorBoundary: AppRootErrorBoundary,
children: [
{
path: paths.app.dashboard.path,
lazy: () => import('./routes/app/dashboard').then(convert(queryClient)),
},
],
},
]);
The features/ Directory
Each feature is a self-contained module. Only include subdirectories that the feature actually needs.
src/features/<feature-name>/
โโโ api/ # API request declarations + react-query hooks
โโโ assets/ # Feature-specific static files
โโโ components/ # Feature-specific UI components
โโโ hooks/ # Feature-specific custom hooks
โโโ stores/ # Feature-specific Zustand stores
โโโ types/ # Feature-specific TypeScript types
โโโ utils/ # Feature-specific utility functions
Rules for Features:
- No cross-feature imports โ
features/auth/ MUST NOT import from features/discussions/
- Compose at app level โ If two features need to interact, compose them in
app/routes/
- Only needed subdirectories โ Don't create empty
hooks/ or stores/ folders
- No barrel files โ Import directly from the source file, not from
index.ts
- Feature independence โ Each feature should be removable without breaking other features
Unidirectional Import Flow
shared (components, hooks, lib, types, utils)
โ
features/<name>
โ
app (routes, providers, router)
The code flows in ONE direction: shared โ features โ app
ESLint Enforcement
'import/no-restricted-paths': ['error', {
zones: [
{ target: './src/features/auth', from: './src/features', except: ['./auth'] },
{ target: './src/features', from: './src/app' },
{
target: ['./src/components', './src/hooks', './src/lib', './src/types', './src/utils'],
from: ['./src/features', './src/app'],
},
],
}]
Path Configuration
Use absolute imports with the @/ prefix:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
Example: import { Button } from '@/components/ui/button'
When to Place Code Where
| What you're creating | Where it goes |
|---|
| A new feature | src/features/<feature-name>/ |
| A component used by one feature | src/features/<feature>/components/ |
| A component used by multiple features | src/components/ |
| A UI primitive (button, input, dialog) | src/components/ui/ |
| An API hook for a feature | src/features/<feature>/api/ |
| A pre-configured library instance | src/lib/ |
| A global store (notifications, theme) | src/stores/ or src/components/ui/<component>/ |
| A feature-specific store | src/features/<feature>/stores/ |
| A shared TypeScript type | src/types/ |
| A shared utility function | src/utils/ |
| A shared custom hook | src/hooks/ |
| A route/page component | src/app/routes/ |
| Test utilities & mocks | src/testing/ |
| Environment variables | src/config/env.ts |
| Path definitions | src/config/paths.ts |