project-structure

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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.
按功能/领域组织代码,而非按文件类型。强制单向代码流:shared → features → app。
这种方法在中大型React、Next.js和TypeScript项目中扩展性良好,同时保持功能独立且易于维护。

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
src/
├── app/                # 应用层(路由、提供者)
│   ├── routes/         # 路由定义 / 页面
│   ├── app.tsx         # 主应用组件
│   ├── provider.tsx    # 全局提供者包装组件
│   └── router.tsx      # 路由配置
├── assets/             # 静态文件(图片、字体)
├── components/         # 共享UI组件
├── config/             # 全局配置、环境变量
├── features/           # 基于功能的模块(核心代码存放于此)
├── hooks/              # 共享钩子
├── lib/                # 预配置库(axios、dayjs等)
├── stores/             # 全局状态存储
├── testing/            # 测试工具与模拟数据
├── types/              # 共享TypeScript类型
└── utils/              # 共享工具函数

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."
每个功能都是一个独立的模块:
src/features/users/
├── api/           # API请求与React Query钩子
├── components/    # 功能专属UI组件
├── hooks/         # 功能专属钩子
├── stores/        # 功能状态(Zustand等)
├── types/         # 功能专属类型
└── utils/         # 功能工具函数
只创建你需要的文件夹。不要“以防万一”创建空文件夹。

Unidirectional Code Flow

单向代码流

┌─────────┐     ┌──────────┐     ┌─────┐
│ shared  │ ──► │ features │ ──► │ app │
└─────────┘     └──────────┘     └─────┘
FromCan 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.
┌─────────┐     ┌──────────┐     ┌─────┐
│ shared  │ ──► │ features │ ──► │ app │
└─────────┘     └──────────┘     └─────┘
来源目录可导入的目录
app
features
shared
(components、hooks、lib、types、utils)
features
shared
shared
仅其他
shared
模块
功能模块之间不能互相导入。在应用层组合各个功能模块。

Decision Guide: Where Does This Code Go?

决策指南:代码应该放在哪里?

Code TypeLocationExample
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
代码类型存放位置示例
路由/页面组件
app/routes/
app/routes/users/page.tsx
功能专属组件
features/[name]/components/
features/users/components/UserCard.tsx
可复用UI组件
components/
components/Button.tsx
功能API调用
features/[name]/api/
features/users/api/getUsers.ts
共享工具函数
utils/
utils/formatDate.ts
功能专属工具函数
features/[name]/utils/
features/users/utils/validateUser.ts
全局状态
stores/
stores/authStore.ts
功能状态
features/[name]/stores/
features/users/stores/userFilterStore.ts
库包装器
lib/
lib/axios.ts
,
lib/dayjs.ts
全局类型
types/
types/api.ts
功能专属类型
features/[name]/types/
features/users/types/user.ts

ESLint Enforcement

ESLint约束

Prevent Cross-Feature Imports

禁止跨功能模块导入

javascript
// .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
        ],
      },
    ],
  },
};
javascript
// .eslintrc.js
module.exports = {
  rules: {
    'import/no-restricted-paths': [
      'error',
      {
        zones: [
          // 禁止跨功能模块导入
          {
            target: './src/features/users',
            from: './src/features',
            except: ['./users'],
          },
          {
            target: './src/features/posts',
            from: './src/features',
            except: ['./posts'],
          },
          // 根据需要添加更多功能模块
        ],
      },
    ],
  },
};

Enforce Unidirectional Flow

强制单向代码流

javascript
// .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'],
          },
        ],
      },
    ],
  },
};
javascript
// .eslintrc.js
module.exports = {
  rules: {
    'import/no-restricted-paths': [
      'error',
      {
        zones: [
          // 功能模块不能从app导入
          {
            target: './src/features',
            from: './src/app',
          },
          // Shared模块不能从features或app导入
          {
            target: [
              './src/components',
              './src/hooks',
              './src/lib',
              './src/types',
              './src/utils',
            ],
            from: ['./src/features', './src/app'],
          },
        ],
      },
    ],
  },
};

Common Patterns

常见模式

Feature API with React Query

结合React Query的功能API

typescript
// 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,
  });
};
typescript
// 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

功能组件

typescript
// 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>
  );
}
typescript
// 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

在应用层组合功能模块

typescript
// 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>
  );
}
typescript
// 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)

不要使用桶文件(index.ts)

typescript
// src/features/users/index.ts
export * from './components/UserList';  // Breaks tree-shaking in Vite
export * from './api/getUsers';
Instead, import directly:
typescript
import { UserList } from '@/features/users/components/UserList';
import { useUsers } from '@/features/users/api/getUsers';
typescript
// src/features/users/index.ts
export * from './components/UserList';  // 破坏Vite的Tree Shaking
export * from './api/getUsers';
正确做法:直接导入
typescript
import { UserList } from '@/features/users/components/UserList';
import { useUsers } from '@/features/users/api/getUsers';

Don't Import Across Features

不要跨功能模块导入

typescript
// src/features/posts/components/PostCard.tsx
import { UserAvatar } from '@/features/users/components/UserAvatar';  // Bad
Instead, lift shared components:
typescript
// Move UserAvatar to src/components/UserAvatar.tsx
import { UserAvatar } from '@/components/UserAvatar';  // Good
typescript
// src/features/posts/components/PostCard.tsx
import { UserAvatar } from '@/features/users/components/UserAvatar';  // 错误示例
正确做法:提取共享组件
typescript
// 将UserAvatar移至src/components/UserAvatar.tsx
import { UserAvatar } from '@/components/UserAvatar';  // 正确示例

Don't Put Everything in Shared

不要把所有内容都放到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
如果一个组件仅被一个功能模块使用,就放在该功能模块内:
// 错误:污染共享组件
src/components/
├── UserCard.tsx          # 仅被users功能模块使用
├── PostCard.tsx          # 仅被posts功能模块使用
└── Button.tsx            # 真正的共享组件

// 正确:功能专属组件
src/features/users/components/UserCard.tsx
src/features/posts/components/PostCard.tsx
src/components/Button.tsx

Next.js App Router Adaptation

Next.js App Router适配

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:
typescript
// src/app/users/page.tsx
import { UserList } from '@/features/users/components/UserList';

export default function UsersPage() {
  return <UserList />;
}
对于使用App Router的Next.js项目,调整结构如下:
src/
├── app/                  # Next.js App Router(路由)
│   ├── (auth)/           # 路由组
│   │   ├── login/
│   │   └── register/
│   ├── dashboard/
│   ├── layout.tsx
│   └── page.tsx
├── components/           # 共享组件
├── features/             # 功能模块(非路由代码)
│   ├── auth/
│   ├── dashboard/
│   └── users/
├── lib/
└── ...
保持路由处理函数尽可能简洁——将逻辑委托给功能模块:
typescript
// 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 (
    @/
    for
    src/
    )
  • Configure ESLint import restrictions
  • Create first feature as a template
  • 创建顶层文件夹结构
  • 设置路径别名(
    @/
    指向
    src/
  • 配置ESLint导入限制规则
  • 创建第一个功能模块作为模板

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
  • 创建
    src/features/[name]/
    目录
  • 仅添加所需的子文件夹(api、components、hooks等)
  • 为该功能模块添加ESLint区域限制
  • 保持功能模块独立——禁止跨功能模块导入

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)
  • 无跨功能模块导入
  • 共享代码存放在共享文件夹中
  • 功能专属代码保留在对应功能模块内
  • 无桶文件(index.ts重导出)

References

参考资料