project-structure
10
总安装量
2
周安装量
#30174
全站排名
安装命令
npx skills add https://github.com/flpbalada/my-opencode-config --skill project-structure
Agent 安装分布
opencode
2
claude-code
2
amp
1
cursor
1
kimi-cli
1
codex
1
Skill 文档
Project Structure: Feature-Based Architecture
Core Principle
Organize code by feature/domain, not by file type. Enforce unidirectional code flow: shared â features â app.
This approach scales well for medium-to-large React, Next.js, and TypeScript projects while keeping features independent and maintainable.
Top-Level Structure
src/
âââ app/ # Application layer (routing, providers)
â âââ routes/ # Route definitions / pages
â âââ app.tsx # Main application component
â âââ provider.tsx # Global providers wrapper
â âââ router.tsx # Router configuration
âââ assets/ # Static files (images, fonts)
âââ components/ # Shared UI components
âââ config/ # Global configuration, env variables
âââ features/ # Feature-based modules (main code lives here)
âââ hooks/ # Shared hooks
âââ lib/ # Pre-configured libraries (axios, dayjs, etc.)
âââ stores/ # Global state stores
âââ testing/ # Test utilities and mocks
âââ types/ # Shared TypeScript types
âââ utils/ # Shared utility functions
Feature Structure
Each feature is a self-contained module:
src/features/users/
âââ api/ # API requests & React Query hooks
âââ components/ # Feature-scoped UI components
âââ hooks/ # Feature-scoped hooks
âââ stores/ # Feature state (Zustand, etc.)
âââ types/ # Feature-specific types
âââ utils/ # Feature utility functions
Only include folders you need. Don’t create empty folders “just in case.”
Unidirectional Code Flow
âââââââââââ ââââââââââââ âââââââ
â shared â ââ⺠â features â ââ⺠â app â
âââââââââââ ââââââââââââ âââââââ
| From | Can Import From |
|---|---|
app |
features, shared (components, hooks, lib, types, utils) |
features |
shared only |
shared |
Other shared modules only |
Features cannot import from each other. Compose features at the app level.
Decision Guide: Where Does This Code Go?
| Code Type | Location | Example |
|---|---|---|
| Route/page component | app/routes/ |
app/routes/users/page.tsx |
| Feature-specific component | features/[name]/components/ |
features/users/components/UserCard.tsx |
| Reusable UI component | components/ |
components/Button.tsx |
| Feature API calls | features/[name]/api/ |
features/users/api/getUsers.ts |
| Shared utility | utils/ |
utils/formatDate.ts |
| Feature utility | features/[name]/utils/ |
features/users/utils/validateUser.ts |
| Global state | stores/ |
stores/authStore.ts |
| Feature state | features/[name]/stores/ |
features/users/stores/userFilterStore.ts |
| Library wrapper | lib/ |
lib/axios.ts, lib/dayjs.ts |
| Global types | types/ |
types/api.ts |
| Feature types | features/[name]/types/ |
features/users/types/user.ts |
ESLint Enforcement
Prevent Cross-Feature Imports
// .eslintrc.js
module.exports = {
rules: {
'import/no-restricted-paths': [
'error',
{
zones: [
// Disables cross-feature imports
{
target: './src/features/users',
from: './src/features',
except: ['./users'],
},
{
target: './src/features/posts',
from: './src/features',
except: ['./posts'],
},
// Add more features as needed
],
},
],
},
};
Enforce Unidirectional Flow
// .eslintrc.js
module.exports = {
rules: {
'import/no-restricted-paths': [
'error',
{
zones: [
// Features cannot import from app
{
target: './src/features',
from: './src/app',
},
// Shared cannot import from features or app
{
target: [
'./src/components',
'./src/hooks',
'./src/lib',
'./src/types',
'./src/utils',
],
from: ['./src/features', './src/app'],
},
],
},
],
},
};
Common Patterns
Feature API with React Query
// src/features/users/api/getUsers.ts
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/axios';
import type { User } from '../types/user';
export const getUsers = async (): Promise<User[]> => {
const response = await api.get('/users');
return response.data;
};
export const useUsers = () => {
return useQuery({
queryKey: ['users'],
queryFn: getUsers,
});
};
Feature Component
// src/features/users/components/UserList.tsx
import { useUsers } from '../api/getUsers';
import { UserCard } from './UserCard';
export function UserList() {
const { data: users, isLoading } = useUsers();
if (isLoading) return <Spinner />;
return (
<div className="grid gap-4">
{users?.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
Composing Features at App Level
// src/app/routes/dashboard/page.tsx
import { UserList } from '@/features/users/components/UserList';
import { PostList } from '@/features/posts/components/PostList';
import { ActivityFeed } from '@/features/activity/components/ActivityFeed';
export function DashboardPage() {
return (
<div className="grid grid-cols-3 gap-6">
<UserList />
<PostList />
<ActivityFeed />
</div>
);
}
Anti-Patterns to Avoid
Don’t Use Barrel Files (index.ts)
// src/features/users/index.ts
export * from './components/UserList'; // Breaks tree-shaking in Vite
export * from './api/getUsers';
Instead, import directly:
import { UserList } from '@/features/users/components/UserList';
import { useUsers } from '@/features/users/api/getUsers';
Don’t Import Across Features
// src/features/posts/components/PostCard.tsx
import { UserAvatar } from '@/features/users/components/UserAvatar'; // Bad
Instead, lift shared components:
// Move UserAvatar to src/components/UserAvatar.tsx
import { UserAvatar } from '@/components/UserAvatar'; // Good
Don’t Put Everything in Shared
If a component is only used by one feature, keep it in that feature:
// Bad: Polluting shared components
src/components/
âââ UserCard.tsx # Only used by users feature
âââ PostCard.tsx # Only used by posts feature
âââ Button.tsx # Actually shared
// Good: Feature-scoped components
src/features/users/components/UserCard.tsx
src/features/posts/components/PostCard.tsx
src/components/Button.tsx
Next.js App Router Adaptation
For Next.js with App Router, adapt the structure:
src/
âââ app/ # Next.js App Router (routes)
â âââ (auth)/ # Route group
â â âââ login/
â â âââ register/
â âââ dashboard/
â âââ layout.tsx
â âââ page.tsx
âââ components/ # Shared components
âââ features/ # Feature modules (non-routing code)
â âââ auth/
â âââ dashboard/
â âââ users/
âââ lib/
âââ ...
Keep route handlers minimal – delegate to feature modules:
// src/app/users/page.tsx
import { UserList } from '@/features/users/components/UserList';
export default function UsersPage() {
return <UserList />;
}
Quick Checklist
Starting a New Project
- Create top-level folder structure
- Set up path aliases (
@/forsrc/) - Configure ESLint import restrictions
- Create first feature as a template
Adding a New Feature
- Create
src/features/[name]/directory - Add only needed subfolders (api, components, hooks, etc.)
- Add ESLint zone restriction for the feature
- Keep feature isolated – no cross-feature imports
Before Code Review
- No cross-feature imports
- Shared code is in shared folders
- Feature-specific code stays in features
- No barrel files (index.ts re-exports)