关于 Feature-Sliced Design
Feature-Sliced Design 是面向前端团队的架构方法技能,通过分层、切片和分段组织可扩展应用。其自定义 `views` 层将页面逻辑与 Next.js App Router 路由分开。
主要功能
- 01七层架构组织代码
- 02按层级限制导入方向
- 03按领域划分切片与公共 API
- 04按用途划分代码段
- 05Next.js App Router 组织方式
- 06Turborepo 应用结构指导
适用场景
- 01搭建新的 Next.js 应用
- 02重构不断扩大的前端代码库
- 03协调团队并行开发
- 04组织 Turborepo 中的独立应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ constellos/ claude-code --skill feature-sliced-design安装后在 Claude Code 中输入/feature-sliced-design确认已加载。
详细介绍
Feature-Sliced Design
目的
Feature-Sliced Design(FSD)是一种架构方法,用于将前端应用组织成标准化、可扩展的结构。它通过分层结构清晰划分关注点,避免循环依赖并提升可维护性。
使用 FSD 的原因:
- 可扩展性:随着应用扩展而自然成长
- 可维护性:清晰的边界让重构更安全
- 团队协作:一致的结构便于并行开发
- 入职培训:新开发者能快速理解架构
自定义的 views 层:
此技能使用 views 代替标准 FSD 的 pages 层,以避免与 Next.js App Router 的 /app 目录混淆。/app 目录仅负责路由(逻辑尽可能精简),而 /src/views 包含实际的页面业务逻辑。
Next.js 集成:
FSD 与 Next.js App Router 可无缝协作,将路由相关逻辑(位于 /app)与业务逻辑(位于 /src/views 及其他 FSD 层)分开。这样既能保持路由配置简洁,又能保留 FSD 的架构优势。
适用场景
以下情况可采用 Feature-Sliced Design:
- 启动需要清晰架构边界的新 Next.js 项目
- 重构缺乏一致结构、不断扩大的代码库
- 与需要标准化组织方式的多开发者团队协作
- 构建业务逻辑复杂、需要划分关注点的应用
- 开发 Turborepo 单体仓库应用,其中每个应用都需要独立的 FSD 结构
- 扩展存在循环依赖问题的应用
- 创建需要长期维护的企业级应用
核心原则
层级结构
FSD 将代码组织为 7 个标准化层(从高到低):
- app - 应用初始化、全局提供程序、路由配置
- processes - 已弃用(功能已移至 features 和 app)
- views - 页面级业务逻辑(自定义命名,替代标准的
pages) - widgets - 跨越多个功能的大型组合式 UI 区块
- features - 具有业务价值的面向用户交互
- entities - 业务领域对象和模型
- shared - 可复用工具、UI 套件、第三方集成
导入规则: 模块只能从层级中严格低于自身的层导入。
┌─────────────────┐
│ app │ ← Can import from all layers below
├─────────────────┤
│ views │ ← Can import: widgets, features, entities, shared
├─────────────────┤
│ widgets │ ← Can import: features, entities, shared
├─────────────────┤
│ features │ ← Can import: entities, shared
├─────────────────┤
│ entities │ ← Can import: shared only
├─────────────────┤
│ shared │ ← Cannot import from any FSD layer
└─────────────────┘
此层级结构可避免循环依赖,并确保架构边界清晰。
views 层与 pages 层
使用 views 而非 pages 的原因:
- Next.js 使用
/app目录进行路由(App Router) - 标准 FSD 使用
pages层来存放页面业务逻辑 - 使用
views可避免混淆路由(/app)与业务逻辑(/src/views)
关注点分离:
/app目录(根级别):仅处理 Next.js 路由,逻辑尽可能精简- 包含
page.tsx、layout.tsx、路由组 - 从
/src/views导入并渲染内容
- 包含
/src/views层(FSD):页面业务逻辑、组件组合- 包含视图组件、模型、API 调用
- 组合 widgets、features、entities
这种分离既能保持路由配置简洁,又能遵循 FSD 的架构原则。
切片
切片是在各层内部按领域划分的分区(app 和 shared 除外,因为它们没有切片)。
示例:
views/dashboard- 仪表盘页面切片widgets/header- 页眉组件切片features/auth- 身份验证功能切片entities/user- 用户实体切片
公共 API 模式:
每个切片都通过 index.ts 导出,以控制其公共接口:
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthState } from './model/types';
// Internal implementation details NOT exported
这样可以避免深层导入并维持封装性。
切片
切片是切片内基于用途的分组:
- ui/ - React 组件、视觉元素
- model/ - 业务逻辑、状态管理、TypeScript 类型
- api/ - API 客户端、数据获取、外部集成
- lib/ - 切片专用的实用函数、辅助工具
- config/ - 配置常量、功能标志
结构示例:
features/
└── auth/
├── ui/
│ ├── LoginForm.tsx
│ └── SignupForm.tsx
├── model/
│ ├── useAuth.ts
│ └── types.ts
├── api/
│ └── authApi.ts
└── index.ts
使用 Next.js App Router 的 FSD
路由架构
Next.js App Router 使用 /app 目录进行路由。FSD 层位于 /src 目录中。
文件组织:
my-nextjs-app/
├── app/ # Next.js routing (minimal logic)
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home route
│ ├── dashboard/
│ │ └── page.tsx # Dashboard route
│ └── settings/
│ └── page.tsx # Settings route
│
├── src/ # FSD layers
│ └── views/ # Page business logic
│ ├── home/
│ │ ├── ui/
│ │ │ └── HomeView.tsx
│ │ └── index.ts
│ ├── dashboard/
│ │ ├── ui/
│ │ │ └── DashboardView.tsx
│ │ ├── model/
│ │ │ └── useDashboard.ts
│ │ └── index.ts
│ └── settings/
│ ├── ui/
│ │ └── SettingsView.tsx
│ └── index.ts
路由页面从视图中导入:
// app/dashboard/page.tsx - Routing only
import { DashboardView } from '@/views/dashboard';
export default function DashboardPage() {
return <DashboardView />;
}
// src/views/dashboard/ui/DashboardView.tsx - Business logic
import { Header } from '@/widgets/header';
import { StatsCard } from '@/features/analytics';
export function DashboardView() {
return (
<div>
<Header />
<StatsCard />
</div>
);
}
独立的 Next.js 结构
独立 Next.js 应用的完整 FSD 结构:
my-nextjs-app/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home route
│ ├── (auth)/ # Route group
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── signup/
│ │ └── page.tsx
│ ├── dashboard/
│ │ └── page.tsx
│ ├── api/ # API routes
│ │ └── users/
│ │ └── route.ts
│ └── not-found.tsx
│
├── src/
│ ├── app/ # App layer (no slices)
│ │ ├── providers/
│ │ │ ├── AuthProvider.tsx
│ │ │ └── QueryProvider.tsx
│ │ ├── styles/
│ │ │ └── globals.css
│ │ └── config/
│ │ └── constants.ts
│ │
│ ├── views/ # Views layer (page logic)
│ │ ├── home/
│ │ ├── dashboard/
│ │ ├── login/
│ │ └── signup/
│ │
│ ├── widgets/ # Widgets layer
│ │ ├── header/
│ │ ├── sidebar/
│ │ ├── footer/
│ │ └── notification-panel/
│ │
│ ├── features/ # Features layer
│ │ ├── auth/
│ │ ├── search/
│ │ ├── theme-toggle/
│ │ └── user-profile/
│ │
│ ├── entities/ # Entities layer
│ │ ├── user/
│ │ ├── post/
│ │ ├── comment/
│ │ └── session/
│ │
│ └── shared/ # Shared layer (no slices)
│ ├── ui/ # UI components
│ │ ├── button/
│ │ ├── input/
│ │ └── card/
│ ├── lib/ # Utilities
│ │ ├── format.ts
│ │ └── validation.ts
│ ├── api/ # API client
│ │ └── client.ts
│ └── config/
│ └── env.ts
│
├── public/
│ ├── images/
│ └── fonts/
│
└── package.json
Turborepo 单仓库结构
Turborepo 单仓库中的 FSD 结构,其中每个应用都有独立的 FSD 组织结构:
turborepo-root/
├── apps/
│ ├── web/ # Consumer-facing app
│ │ ├── app/ # Next.js routing
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ └── shop/
│ │ │ └── page.tsx
│ │ ├── src/ # Independent FSD structure
│ │ │ ├── app/
│ │ │ ├── views/
│ │ │ │ ├── home/
│ │ │ │ └── shop/
│ │ │ ├── widgets/
│ │ │ │ ├── product-grid/
│ │ │ │ └── shopping-cart/
│ │ │ ├── features/
│ │ │ │ ├── add-to-cart/
│ │ │ │ └── checkout/
│ │ │ ├── entities/
│ │ │ │ ├── product/
│ │ │ │ └── order/
│ │ │ └── shared/
│ │ └── package.json
│ │
│ └── admin/ # Admin dashboard app
│ ├── app/ # Next.js routing
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── products/
│ │ └── page.tsx
│ ├── src/ # Independent FSD structure
│ │ ├── app/
│ │ ├── views/
│ │ │ ├── dashboard/
│ │ │ └── products/
│ │ ├── widgets/
│ │ │ ├── admin-header/
│ │ │ └── stats-panel/
│ │ ├── features/
│ │ │ ├── product-editor/
│ │ │ └── user-management/
│ │ ├── entities/
│ │ │ ├── product/
│ │ │ └── admin/
│ │ └── shared/
│ └── package.json
│
├── packages/ # Optional shared packages
│ ├── ui/ # Shared UI components (can mirror shared/ui)
│ │ ├── button/
│ │ └── input/
│ ├── utils/ # Shared utilities
│ │ └── validation.ts
│ └── types/ # Shared TypeScript types
│ └── common.ts
│
├── turbo.json
└── package.json
Turborepo 的主要原则:
- 每个应用(
web、admin)都有自己的完整 FSD 结构 - 应用彼此独立,不从 FSD 层跨应用导入
- 共享代码放在
packages/目录中(可选) - 使用
workspace:*协议作为包依赖
层定义
app 层
用途: 应用级设置、初始化和全局配置。
职责:
- 全局提供程序(主题、身份验证、查询客户端)
- 根样式和 CSS 导入
- 应用级配置
- 错误边界
导入规则: 可以从所有下层导入(views、widgets、features、entities、shared)。
无切片: app 层直接包含各个分段(providers/、styles/、config/)。
示例:
// src/app/providers/Providers.tsx
'use client';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/shared/api/queryClient';
import { AuthProvider } from '@/features/auth';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
{children}
</AuthProvider>
</QueryClientProvider>
);
}
// app/layout.tsx
import { Providers } from '@/app/providers/Providers';
import '@/app/styles/globals.css';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
views 层
用途: 页面级业务逻辑和组件组合。
职责:
- 将 widgets、features 和 entities 组合成完整页面
- 页面专属的状态管理
- 页面的数据获取
- SEO 元数据
导入规则: 可以从 widgets、features、entities、shared 导入。
包含切片: 每个页面都有自己的切片(例如,views/dashboard、views/settings)。
示例:
// src/views/dashboard/ui/DashboardView.tsx
import { Header } from '@/widgets/header';
import { Sidebar } from '@/widgets/sidebar';
import { StatsCard } from '@/features/analytics';
import { RecentActivity } from '@/features/activity';
import { User } from '@/entities/user';
interface DashboardViewProps {
user: User;
}
export function DashboardView({ user }: DashboardViewProps) {
return (
<div className="dashboard">
<Header user={user} />
<div className="dashboard-content">
<Sidebar />
<main>
<StatsCard userId={user.id} />
<RecentActivity userId={user.id} />
</main>
</div>
</div>
);
}
// src/views/dashboard/index.ts
export { DashboardView } from './ui/DashboardView';
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
import { getCurrentUser } from '@/entities/user';
export default async function DashboardPage() {
const user = await getCurrentUser();
return <DashboardView user={user} />;
}
widgets 层
用途: 大型、自包含的复合 UI 区块,组合多个功能。
职责:
- 可在多个页面间复用
- 组合多个功能
- 复杂 UI 布局(页眉、侧边栏、页脚)
- 导航组件
导入规则: 可以从 features、entities、shared 导入。
包含切片: 每个 widget 都有自己的切片(例如,widgets/header、widgets/sidebar)。
示例:
// src/widgets/header/ui/Header.tsx
import { SearchBar } from '@/features/search';
import { UserMenu } from '@/features/user-menu';
import { NotificationBell } from '@/features/notifications';
import { User } from '@/entities/user';
import { Logo } from '@/shared/ui/logo';
interface HeaderProps {
user: User;
}
export function Header({ user }: HeaderProps) {
return (
<header className="header">
<Logo />
<SearchBar />
<div className="header-actions">
<NotificationBell userId={user.id} />
<UserMenu user={user} />
</div>
</header>
);
}
// src/widgets/header/index.ts
export { Header } from './ui/Header';
features 层
用途: 具有明确业务价值、面向用户的交互和业务逻辑。
职责:
- 特定用户操作(登录、加入购物车、点赞帖子)
- 功能专属的状态管理
- 业务逻辑和验证
- 功能的 API 交互
导入规则: 可以从 entities、shared 导入。
包含切片: 每个功能都有自己的切片(例如,features/auth、features/search)。
示例:
// src/features/auth/model/types.ts
export interface LoginCredentials {
email: string;
password: string;
}
// src/features/auth/api/login.ts
import { User } from '@/entities/user';
import { apiClient } from '@/shared/api/client';
import type { LoginCredentials } from '../model/types';
export async function login(credentials: LoginCredentials): Promise<User> {
const response = await apiClient.post('/auth/login', credentials);
return response.data;
}
// src/features/auth/ui/LoginForm.tsx
'use client';
import { useState } from 'react';
import { login } from '../api/login';
import { Button } from '@/shared/ui/button';
import { Input } from '@/shared/ui/input';
export function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await login({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<Input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<Button type="submit">Login</Button>
</form>
);
}
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { login } from './api/login';
export type { LoginCredentials } from './model/types';
entities 层
用途: 业务领域对象和核心数据模型。
职责:
- 表示业务概念的数据结构
- 实体专用的实用工具
- 基础 API 操作(CRUD)
- 类型定义
导入规则: 只能从 shared 导入。
包含切片: 每个实体都有自己的切片(例如,entities/user、entities/post)。
示例:
// src/entities/user/model/types.ts
export interface User {
id: string;
name: string;
email: string;
avatar?: string;
role: 'admin' | 'user';
}
// src/entities/user/api/getUser.ts
import { apiClient } from '@/shared/api/client';
import type { User } from '../model/types';
export async function getUser(id: string): Promise<User> {
const response = await apiClient.get(`/users/${id}`);
return response.data;
}
export async function getCurrentUser(): Promise<User> {
const response = await apiClient.get('/users/me');
return response.data;
}
// src/entities/user/ui/UserCard.tsx
import type { User } from '../model/types';
import { Avatar } from '@/shared/ui/avatar';
interface UserCardProps {
user: User;
}
export function UserCard({ user }: UserCardProps) {
return (
<div className="user-card">
<Avatar src={user.avatar} alt={user.name} />
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
</div>
);
}
// src/entities/user/index.ts
export { UserCard } from './ui/UserCard';
export { getUser, getCurrentUser } from './api/getUser';
export type { User } from './model/types';
shared 层
用途: 可复用的实用工具、UI 组件和第三方集成。
职责:
- UI 套件(按钮、输入框、卡片组件)
- 辅助函数(格式化工具、验证器)
- API 客户端配置
- 常量和环境变量
- 第三方库集成
导入规则: 不能从任何 FSD 层导入(只能导入外部包)。
无切片: 直接包含各个分段(ui/、lib/、api/、config/)。
示例:
// src/shared/ui/button/Button.tsx
import { type ButtonHTMLAttributes } from 'react';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
}
export function Button({
variant = 'primary',
size = 'md',
className,
children,
...props
}: ButtonProps) {
return (
<button
className={`button button--${variant} button--${size} ${className}`}
{...props}
>
{children}
</button>
);
}
// src/shared/lib/format.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('en-US').format(date);
}
export function formatCurrency(amount: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(amount);
}
// src/shared/api/client.ts
import axios from 'axios';
export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
// src/shared/config/env.ts
export const env = {
apiUrl: process.env.NEXT_PUBLIC_API_URL!,
nodeEnv: process.env.NODE_ENV,
} as const;
工作流
步骤 1:设置层目录
创建 FSD 文件夹结构:
mkdir -p src/{app,views,widgets,features,entities,shared}
mkdir -p src/app/{providers,styles,config}
mkdir -p src/shared/{ui,lib,api,config}
步骤 2:创建第一个实体
从实体(底层)开始。定义核心业务模型:
// src/entities/user/model/types.ts
export interface User {
id: string;
name: string;
email: string;
}
// src/entities/user/api/getUser.ts
export async function getUser(id: string): Promise<User> {
// API implementation
}
// src/entities/user/index.ts
export type { User } from './model/types';
export { getUser } from './api/getUser';
步骤 3:使用实体构建功能
创建使用实体的功能:
// src/features/user-profile/ui/UserProfile.tsx
import { User } from '@/entities/user'; // ✅ Feature imports entity
export function UserProfile({ user }: { user: User }) {
return <div>{user.name}</div>;
}
// src/features/user-profile/index.ts
export { UserProfile } from './ui/UserProfile';
步骤 4:从功能组合组件
构建组合式组件:
// src/widgets/header/ui/Header.tsx
import { UserProfile } from '@/features/user-profile'; // ✅ Widget imports feature
import { SearchBar } from '@/features/search';
export function Header({ user }) {
return (
<header>
<SearchBar />
<UserProfile user={user} />
</header>
);
}
步骤 5:组装视图
创建页面级视图:
// src/views/dashboard/ui/DashboardView.tsx
import { Header } from '@/widgets/header'; // ✅ View imports widget
export function DashboardView() {
return (
<div>
<Header />
{/* More content */}
</div>
);
}
// src/views/dashboard/index.ts
export { DashboardView } from './ui/DashboardView';
步骤 6:连接到应用路由器
将视图接入 Next.js 路由:
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
export default function DashboardPage() {
return <DashboardView />;
}
导入规则和依赖关系
允许的导入模式
// ✅ Layer importing from layer below
import { User } from '@/entities/user'; // Feature → Entity
import { LoginForm } from '@/features/auth'; // Widget → Feature
import { Header } from '@/widgets/header'; // View → Widget
// ✅ Any layer importing from shared
import { Button } from '@/shared/ui/button';
import { formatDate } from '@/shared/lib/format';
// ✅ Slice importing from different slice in lower layer
import { User } from '@/entities/user'; // features/auth → entities/user
import { Post } from '@/entities/post'; // features/like → entities/post
禁止的导入模式
// ❌ Layer importing from same or higher layer
import { DashboardView } from '@/views/dashboard'; // Feature → View (upward)
import { Header } from '@/widgets/header'; // Feature → Widget (upward)
import { LoginForm } from '@/features/login'; // features/auth → features/login (same layer)
// ❌ Cross-slice imports within same layer
import { SearchBar } from '@/features/search'; // features/auth → features/search
// ❌ Shared importing from FSD layers
import { User } from '@/entities/user'; // shared/lib → entities/user
有效与无效示例
无效(跨功能导入):
// ❌ src/features/search/ui/SearchBar.tsx
import { LoginForm } from '@/features/auth'; // Same layer import
有效(提取到组件):
// ✅ src/widgets/navbar/ui/Navbar.tsx
import { SearchBar } from '@/features/search';
import { LoginForm } from '@/features/auth';
export function Navbar() {
return (
<nav>
<SearchBar />
<LoginForm />
</nav>
);
}
修复循环依赖
问题:
// features/auth imports features/user-settings
// features/user-settings imports features/auth
// ❌ Circular dependency
解决方案 1:提取到实体
// Move shared logic to entities/user
// Both features import from entities/user
// ✅ No circular dependency
解决方案 2:提取到组件
// Create widgets/user-panel that imports both features
// ✅ Widget layer can import from features
强制执行公共 API
始终使用 index.ts 控制导出:
// src/features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export type { AuthState } from './model/types';
// ❌ Do NOT export internal helpers
// export { validatePassword } from './lib/validation'; // Keep internal
仅从公共 API 导入:
// ✅ Correct
import { LoginForm } from '@/features/auth';
// ❌ Wrong (deep import)
import { LoginForm } from '@/features/auth/ui/LoginForm';
分段模式
ui/ 分段
用途: React 组件和视觉元素。
适用场景:
- 任何 React 组件
- UI 组合
- 视觉呈现
示例:
// src/entities/user/ui/UserCard.tsx
import type { User } from '../model/types';
export function UserCard({ user }: { user: User }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
model/ 分段
用途: 业务逻辑、状态管理和类型定义。
适用场景:
- TypeScript 接口和类型
- 用于管理状态的 React 钩子
- 业务逻辑函数
- 数据转换
示例:
// src/features/auth/model/useAuth.ts
'use client';
import { create } from 'zustand';
import type { User } from '@/entities/user';
interface AuthState {
user: User | null;
isAuthenticated: boolean;
login: (user: User) => void;
logout: () => void;
}
export const useAuth = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }),
logout: () => set({ user: null, isAuthenticated: false }),
}));
api/ 分段
用途: API 客户端、数据获取和外部集成。
适用场景:
- HTTP 请求
- WebSocket 连接
- 第三方 API 集成
- 服务端操作(Next.js)
示例:
// src/entities/user/api/userApi.ts
'use server';
import { apiClient } from '@/shared/api/client';
import type { User } from '../model/types';
export async function fetchUsers(): Promise<User[]> {
const response = await apiClient.get('/users');
return response.data;
}
export async function createUser(data: Omit<User, 'id'>): Promise<User> {
const response = await apiClient.post('/users', data);
return response.data;
}
lib/ 分段
用途: 专用于切片的工具函数和辅助函数。
适用场景:
- 切片专用工具
- 验证函数
- 数据转换辅助函数
示例:
// src/features/auth/lib/validation.ts
import { z } from 'zod';
export const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
export function validateLogin(data: unknown) {
return loginSchema.parse(data);
}
config/ 分段
用途: 配置常量和功能标志。
适用场景:
- 功能专用常量
- 配置对象
- 功能标志
示例:
// src/app/config/theme.ts
export const theme = {
colors: {
primary: '#0070f3',
secondary: '#ff4081',
},
breakpoints: {
sm: '640px',
md: '768px',
lg: '1024px',
},
} as const;
迁移策略
将现有 Next.js 应用迁移到 FSD
自底向上的方法(推荐):
-
从共享层开始
- 将通用 UI 组件提取到
shared/ui/ - 将工具函数移至
shared/lib/ - 在
shared/api/中配置 API 客户端
- 将通用 UI 组件提取到
-
定义实体
- 确定业务领域对象(User、Post、Comment)
- 在
entities/{name}/model/中创建实体类型 - 将 CRUD 操作移至
entities/{name}/api/
-
提取功能
- 确定用户交互(登录、搜索、点赞)
- 在
features/{name}/中创建功能切片 - 在功能中使用实体
-
构建组件
- 确定组合式 UI 区块(Header、Sidebar)
- 在
widgets/{name}/中创建组件切片 - 在组件中组合功能
-
组织视图
- 将页面逻辑从
/app移至/src/views - 将路由保留在
/app,将业务逻辑放在/src/views - 在视图中组合组件
- 将页面逻辑从
-
配置应用层
- 将全局提供程序移至
app/providers/ - 将全局样式移至
app/styles/
- 将全局提供程序移至
处理现有代码
渐进式迁移:
- 每次迁移一个页面
- 从最简单的页面开始
- 过渡期间同时使用新旧结构
- 迁移时更新导入
全程测试:
- 每迁移一层后运行测试
- 确保功能未受影响
- 验证导入路径正确
最佳实践
保持切片隔离:
- 同一层内的切片之间不要相互导入
- 每个切片都应保持独立
- 将共享逻辑提取到较低层
采用公共 API 模式:
- 始终通过
index.ts导出 - 避免深层导入
- 便于重构
将测试与代码放在一起:
features/
└── auth/
├── ui/
│ ├── LoginForm.tsx
│ └── LoginForm.test.tsx # Test next to implementation
└── index.ts
避免“巨型切片”:
- 让切片专注于单一职责
- 将大型切片拆分成多个较小的切片
- 将通用逻辑提取到共享层
按领域而非技术命名:
- ✅
features/product-search - ❌
features/search-bar-component
使用 TypeScript 严格模式:
{
"compilerOptions": {
"strict": true
}
}
记录架构决策:
- 保留 ADR(架构决策记录)
- 记录某些切片存在的原因
- 说明层边界的决策
常见模式
共享 UI 组件
// src/shared/ui/button/Button.tsx
export function Button({ children, ...props }) {
return <button {...props}>{children}</button>;
}
// Usage in feature
import { Button } from '@/shared/ui/button';
API 客户端设置
// src/shared/api/client.ts
import axios from 'axios';
export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
});
// Usage in entity
import { apiClient } from '@/shared/api/client';
使用功能处理表单
// src/features/product-form/ui/ProductForm.tsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { productSchema } from '../lib/validation';
import { createProduct } from '../api/createProduct';
export function ProductForm() {
const { register, handleSubmit } = useForm({
resolver: zodResolver(productSchema),
});
return <form onSubmit={handleSubmit(createProduct)}>...</form>;
}
身份验证集成
// src/features/auth/model/useAuth.ts
export const useAuth = create<AuthState>((set) => ({...}));
// src/widgets/header/ui/Header.tsx
import { useAuth } from '@/features/auth';
export function Header() {
const { user } = useAuth();
return <header>Welcome, {user?.name}</header>;
}
使用服务端组件获取数据
// app/dashboard/page.tsx
import { DashboardView } from '@/views/dashboard';
import { getUser } from '@/entities/user';
export default async function DashboardPage() {
const user = await getUser('current');
return <DashboardView user={user} />;
}
// src/views/dashboard/ui/DashboardView.tsx
import type { User } from '@/entities/user';
export function DashboardView({ user }: { user: User }) {
return <div>Welcome, {user.name}</div>;
}
故障排除
循环依赖
问题: 两个切片相互导入。
解决方案:
- 将共享逻辑提取到较低层(通常是实体层或共享层)
- 创建一个同时导入两者的较高层(组件)
- 检查其中一个切片是否应该拆分成多个切片
导入路径问题
问题: TypeScript 无法解析 @/ 导入。
解决方案: 在 tsconfig.json 中配置路径别名:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/app/*": ["src/app/*"],
"@/views/*": ["src/views/*"],
"@/widgets/*": ["src/widgets/*"],
"@/features/*": ["src/features/*"],
"@/entities/*": ["src/entities/*"],
"@/shared/*": ["src/shared/*"]
}
}
}
构建错误
问题: 重构后 Next.js 无法找到模块。
解决方案:
- 清除
.next目录:rm -rf .next - 重新安装依赖项:
npm install - 重启开发服务器:
npm run dev
配置
TypeScript 路径别名
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/app/*": ["src/app/*"],
"@/views/*": ["src/views/*"],
"@/widgets/*": ["src/widgets/*"],
"@/features/*": ["src/features/*"],
"@/entities/*": ["src/entities/*"],
"@/shared/*": ["src/shared/*"]
}
},
"include": ["src", "app"]
}
ESLint 导入规则(可选)
// .eslintrc.js
module.exports = {
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['@/views/*', '@/widgets/*'],
message: 'Features cannot import from views or widgets',
},
],
},
],
},
};
参考资料
- FSD 官方文档 - 完整的方法指南
- 使用 Next.js 的 FSD 指南 - Next.js 集成模式
- FSD GitHub 仓库 - 源文档
- 前端 Monorepo 架构 - Turborepo 和 FSD
- FSD 教程 - 分步实施指南
- FSD 示例 - 使用 FSD 的实际应用