feature-sliced-design by aiko-atami/fsd
npx skills add https://github.com/aiko-atami/fsd --skill feature-sliced-design一项用于基于 Feature-Sliced Design 原则搭建和组织前端应用程序的架构方法技能。
Feature-Sliced Design v2.1 是一套用于组织前端代码的规则和约定集合,旨在使项目在面对不断变化的业务需求时更易于理解、维护和保持稳定。
版本 2.1 引入了“页面优先”方法 - 将更多代码保留在页面和部件中,而不是过早地将其提取到功能和实体中。
FSD v2.1 的基本原则:将代码保留在使用它的地方,直到你需要重用它为止。
不要立即将所有内容提取到实体和功能中,而是首先将代码保留在页面和部件中。只有在实际需要重用时,才将代码移动到更低的层级。
✅ 仅在一个页面使用的大型 UI 块 ✅ 特定于页面的表单及其验证逻辑 ✅ 用于页面特定数据的数据获取和状态管理 ✅ 仅服务于该页面/部件的业务逻辑 ✅ 仅在此处需要的API 交互
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
FSD 使用6 个活跃的标准化层级,按职责和依赖关系组织。层级从最具体(顶部)到最通用(底部)排序:
app/ ← 应用初始化、提供者、全局样式
pages/ ← 具有自身逻辑和路由的完整页面组合
widgets/ ← 具有自身逻辑的大型复合 UI 块
features/ ← 可重用的用户交互和业务功能
entities/ ← 可重用的业务实体(用户、产品、订单)
shared/ ← 可重用的基础设施代码(UI 套件、工具、API)
注意:历史上,FSD 包含 processes/ 作为第 7 层,但在 v2.1 中已弃用。如果你正在使用它,请将代码移动到 features/,如果需要,可以从 app/ 获得帮助。
导入规则:模块只能从严格位于其下方的层级导入。
features/ → entities/, shared/pages/ → widgets/, features/, entities/, shared/entities/ → features/ (向上导入)features/comments/ → features/posts/ (同层交叉导入)切片按业务领域含义对代码进行分组。每个切片代表一个特定的业务概念:
features/
├── auth/ ← 身份验证功能
├── comments/ ← 评论功能
└── post-editor/ ← 帖子编辑功能
entities/
├── user/ ← 用户业务实体
├── product/ ← 产品业务实体
└── order/ ← 订单业务实体
关键规则:
片段按技术目的在切片内对代码进行分组:
features/
└── auth/
├── ui/ ← React 组件、样式、格式化器
├── api/ ← API 请求、数据类型、映射器
├── model/ ← 状态管理、业务逻辑、存储
├── lib/ ← 此切片内部使用的工具函数
├── config/ ← 配置、功能开关
└── index.ts ← 公共 API(仅导出其他切片需要的内容)
标准片段:
ui - UI 组件、样式、日期格式化器api - 后端交互、请求函数、数据类型model - 数据模型、状态存储、业务逻辑lib - 此切片所需的工具函数config - 配置文件、功能开关每个切片必须通过一个索引文件定义公共 API:
// features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export { loginUser } from './api/loginUser';
// 未导出的内部文件对切片保持私有
规则:切片外部的模块只能从公共 API 导入,不能从内部文件导入。
v2.1 新增:你现在可以使用 @x 表示法在同一层(通常是实体层)的切片之间创建显式连接。
这允许实体在存在合法业务关系时相互引用:
// entities/user/index.ts
export { UserCard } from './ui/UserCard';
export { userModel } from './model';
// entities/user/@x/order.ts
// 专门为订单实体准备的交叉导入 API
export { UserOrderHistory } from './ui/UserOrderHistory';
export { getUserOrders } from './api/getUserOrders';
// entities/order/index.ts
import { UserOrderHistory } from '@/entities/user/@x/order';
// 现在订单可以从用户的交叉导入 API 导入
何时使用交叉导入:
重要:切片之间的常规交叉导入(不使用 @x)仍然不被允许。使用 @x 表示法使交叉依赖关系变得明确和可控。
应用范围的设置、提供者、路由设置。
app/
├── providers/ ← Redux Provider、React Query、主题提供者
├── styles/ ← 全局 CSS、重置样式、主题变量
├── index.tsx ← 应用入口点
└── router.tsx ← 路由配置
具有自身逻辑和数据管理的路由级组合。
pages/
├── home/
│ ├── ui/
│ │ ├── HomePage.tsx
│ │ ├── HeroSection.tsx ← 大型 UI 块
│ │ └── FeaturesGrid.tsx
│ ├── model/
│ │ └── useHomeData.ts ← 页面特定状态
│ ├── api/
│ │ └── fetchHomeData.ts ← 页面特定 API
│ └── index.ts
├── profile/
│ ├── ui/
│ │ ├── ProfilePage.tsx
│ │ ├── ProfileForm.tsx ← 特定于此页面的表单
│ │ └── ProfileStats.tsx
│ ├── model/
│ │ ├── profileStore.ts ← 个人资料页面的状态
│ │ └── validation.ts ← 表单验证
│ ├── api/
│ │ ├── updateProfile.ts
│ │ └── fetchProfile.ts
│ └── index.ts
└── settings/
v2.1 方法:页面现在可以包含:
仅当需要在其他地方重用代码时才提取到更低层级。
具有自身逻辑的复杂复合 UI 块,在多个页面中使用。
widgets/
├── header/
│ ├── ui/
│ │ ├── Header.tsx
│ │ ├── Navigation.tsx
│ │ └── UserMenu.tsx
│ ├── model/
│ │ └── headerStore.ts ← 部件状态
│ ├── api/
│ │ └── fetchNotifications.ts ← 部件特定 API
│ └── index.ts
├── sidebar/
│ ├── ui/
│ ├── model/
│ │ └── sidebarState.ts
│ └── index.ts
└── footer/
v2.1 方法:部件不再仅仅是组合块。它们可以包含:
仅当其他部件或页面需要时,才将代码提取到实体/功能层。
可重用的用户交互和完整的业务功能,在多个地方使用。
features/
├── auth/
│ ├── ui/
│ │ ├── LoginForm.tsx
│ │ └── RegisterForm.tsx
│ ├── model/
│ │ └── useAuth.ts
│ ├── api/
│ │ ├── login.ts
│ │ └── register.ts
│ └── index.ts
├── add-to-cart/
├── like-post/
└── comment-create/
v2.1 方法:仅在以下情况下创建功能:
不要过早创建功能。 如果用户交互仅在一个地方使用,请将其保留在页面或部件中,直到你实际需要重用它。
可重用的业务实体 - 在整个应用程序中使用的核心领域模型。
entities/
├── user/
│ ├── ui/
│ │ ├── UserCard.tsx
│ │ └── UserAvatar.tsx
│ ├── model/
│ │ ├── types.ts
│ │ └── userStore.ts
│ ├── api/
│ │ └── userApi.ts
│ ├── @x/
│ │ └── order.ts ← 用于订单的交叉导入 API
│ └── index.ts
├── product/
└── order/
v2.1 方法:仅在以下情况下创建实体:
不要过早提取实体。 如果数据结构仅在一个地方使用,请将其保留在那里,直到你需要共享它。
可重用的基础设施代码,不包含业务逻辑。
shared/
├── ui/ ← UI 套件组件
│ ├── Button/
│ ├── Input/
│ └── Modal/
├── lib/ ← 工具函数
│ ├── formatDate/
│ ├── debounce/
│ └── classnames/
├── api/ ← API 客户端设置、基础配置
│ ├── client.ts
│ └── apiRoutes.ts ← 路由常量(v2.1:允许!)
├── config/ ← 环境变量、常量
│ ├── env.ts
│ └── appConfig.ts
├── assets/ ← 图片、字体、图标
│ ├── logo.svg ← 公司徽标(v2.1:允许!)
│ └── icons/
└── types/ ← 通用 TypeScript 类型
v2.1 更新:共享层现在可以包含应用感知代码:
仍然不允许:
共享层中没有切片 - 仅按片段组织。片段可以在共享层内相互导入。
从简单开始 - 将 API 逻辑保留在页面中,直到你需要重用它:
// pages/profile/api/fetchProfile.ts
export const fetchProfile = (id: string) =>
apiClient.get(`/users/${id}`);
// pages/profile/model/useProfile.ts
export const useProfile = (id: string) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchProfile(id).then(setUser);
}, [id]);
return user;
};
// pages/profile/ui/ProfilePage.tsx
import { useProfile } from '../model/useProfile';
const ProfilePage = () => {
const user = useProfile('123');
return <div>{user?.name}</div>;
};
仅当其他页面需要相同的 API 时才移动到实体层:
// entities/user/api/userApi.ts
export const fetchUser = (id: string) =>
apiClient.get(`/users/${id}`);
// entities/user/model/userStore.ts
export const useUser = (id: string) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(id).then(setUser);
}, [id]);
return user;
};
// entities/user/index.ts
export { useUser } from './model/userStore';
export type { User } from './model/types';
// 现在多个页面可以使用它:
// pages/profile/ui/ProfilePage.tsx
// pages/user-list/ui/UserListPage.tsx
import { useUser } from '@/entities/user';
功能可以使用实体和其他功能:
// features/post-card/ui/PostCard.tsx
import { UserAvatar } from '@/entities/user';
import { LikeButton } from '@/features/like-post';
import { CommentButton } from '@/features/comment-create';
export const PostCard = ({ post }) => (
<article>
<UserAvatar userId={post.authorId} />
<h2>{post.title}</h2>
<p>{post.content}</p>
<div>
<LikeButton postId={post.id} />
<CommentButton postId={post.id} />
</div>
</article>
);
路由应在应用层定义,页面在页面层组合:
// app/router.tsx
import { HomePage } from '@/pages/home';
import { ProfilePage } from '@/pages/profile';
export const router = createBrowserRouter([
{ path: '/', element: <HomePage /> },
{ path: '/profile/:id', element: <ProfilePage /> },
]);
// app/index.tsx
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
export const App = () => <RouterProvider router={router} />;
// shared/ui/Button/Button.tsx
export const Button = ({ children, onClick, variant = 'primary' }) => (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
);
// shared/ui/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
// 在功能中使用
import { Button } from '@/shared/ui/Button';
const LoginForm = () => (
<form>
<Button variant="primary">登录</Button>
</form>
);
使用路径别名进行简洁的导入:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/app/*": ["src/app/*"],
"@/pages/*": ["src/pages/*"],
"@/widgets/*": ["src/widgets/*"],
"@/features/*": ["src/features/*"],
"@/entities/*": ["src/entities/*"],
"@/shared/*": ["src/shared/*"]
}
}
}
从以下问题开始:“这段代码在哪里使用?”
它是可重用的基础设施吗?
它是一个完整的用户操作吗?
它是一个业务领域概念吗?
它是应用范围的设置吗?
“带有验证的用户个人资料表单”
pages/profile/ui/ProfileForm.tsxfeatures/profile-form/“产品卡片组件”
pages/products/ui/ProductCard.tsxwidgets/product-card/ 或 entities/product/ui/ProductCard.tsxshared/ui/Card/“获取产品数据”
pages/product-detail/api/fetchProduct.tsentities/product/api/productApi.ts“模态管理器”
shared/ui/modal-manager/“当有疑问时,将其保留在页面/部件中。当你实际需要重用它时,再提取到更低的层级。”
不要试图预测可重用性。等待实际的重用出现,然后进行重构。
❌ 过早提取(v2.1 关键反模式)
// 在不知道是否需要之前立即创建实体/功能
// entities/user-profile-form/ ← 仅在一个页面使用!
✅ 解决方案:保留在页面中,直到实际需要在其他地方使用
// pages/profile/ui/ProfileForm.tsx ← 从这里开始
// 仅当另一个页面需要它时才移动到 features/
❌ 同一层切片之间的交叉导入
// features/comments/ui/CommentList.tsx
import { likePost } from '@/features/like-post'; // 错误!
✅ 解决方案:对于实体使用 @x 表示法,或在更高层级组合
// 对于具有业务关系的实体:
// entities/user/@x/order.ts
export { UserOrderHistory } from './ui/UserOrderHistory';
// 对于功能,在页面层级组合:
// pages/post/ui/PostPage.tsx
import { CommentList } from '@/features/comments';
import { LikeButton } from '@/features/like-post';
❌ 共享层中的业务逻辑
// shared/lib/userHelpers.ts
export const calculateUserReputation = (user) => { ... }; // 错误!
✅ 解决方案:移动到实体层
// entities/user/lib/calculateReputation.ts
export const calculateUserReputation = (user) => { ... };
❌ 绕过公共 API
import { LoginButton } from '@/features/auth/ui/LoginButton'; // 错误!
✅ 使用公共 API
import { LoginButton } from '@/features/auth'; // 正确!
❌ 上帝切片(职责过多)
// features/user-management/ ← 过于宽泛
// - 登录、注册、个人资料编辑、密码重置等
✅ 拆分为聚焦的功能
// features/auth/
// features/profile-edit/
// features/password-reset/
如果你有一个现有的 FSD 2.0 项目,迁移到 2.1 是非破坏性的。你可以逐步采用“页面优先”方法。
审计当前的功能和实体
将页面特定代码移回页面
pages/[page]/ui/pages/[page]/api/pages/[page]/model/将部件特定代码移动到部件
将真正可重用的代码保留在功能/实体中
使用应用感知代码更新共享层
shared/api/routes.tsshared/assets/弃用流程层
考虑使用 @x 表示法
之前 (v2.0):
features/
└── user-profile-form/ ← 仅在个人资料页面使用
├── ui/
├── model/
└── api/
pages/
└── profile/
└── ui/
└── ProfilePage.tsx ← 仅仅是组合
之后 (v2.1):
pages/
└── profile/
├── ui/
│ ├── ProfilePage.tsx
│ └── ProfileForm.tsx ← 移动到这里
├── model/
│ └── profileStore.ts ← 移动到这里
└── api/
└── updateProfile.ts ← 移动到这里
将现有代码迁移到 FSD 时:
shared/entities/features/app/逐步进行 - 你不需要一次性重构所有内容。
features/
└── todo-list/
├── ui/
│ └── TodoList.tsx
├── model/
│ ├── todoSlice.ts ← Redux slice
│ ├── selectors.ts ← 选择器
│ └── thunks.ts ← 异步操作
└── index.ts
entities/
└── user/
├── ui/
├── api/
│ └── userQueries.ts ← React Query hooks
├── model/
│ └── types.ts
└── index.ts
将 FSD 结构放在 src/ 文件夹中,以避免与 Next.js 的 app/ 或 pages/ 文件夹冲突:
my-nextjs-project/
├── app/ ← Next.js App Router(如果使用)
├── pages/ ← Next.js Pages Router(如果使用)
└── src/
├── app/ ← FSD 应用层
├── pages/ ← FSD 页面层
├── widgets/
├── features/
├── entities/
└── shared/
project/
├── src/
│ ├── app/
│ ├── pages/
│ ├── widgets/
│ ├── features/
│ ├── entities/
│ └── shared/
├── tsconfig.json
├── vite.config.ts
└── package.json
project/
└── src/
├── app/
├── pages/
├── widgets/
├── features/
├── entities/
└── shared/
层级选择:
app/pages/widgets/features/entities/shared/导入方向:App → Pages → Widgets → Features → Entities → Shared
公共 API:始终为切片创建 index.ts 以导出公共接口
有关更详细的信息和边缘情况:
在项目中实施 FSD 时:
Steiger 是一个官方的 linter,可帮助自动执行 FSD 规则:
安装:
npm install -D @feature-sliced/steiger
用法:
npx steiger src
Steiger 已可用于生产并积极维护。它是确保你的团队始终遵循 FSD 约定的最佳方式。
在以下情况下触发此技能:
“从简单开始,需要时再提取。”
不要试图预测未来的架构。首先在页面和部件中构建功能。当你看到实际的重用模式出现时,再提取到功能和实体中。这会导致:
此技能提供了使用 Feature-Sliced Design v2.1 方法论构建任何前端应用程序的基础知识。始终优先考虑代码内聚性,在提取之前等待实际重用,并保持适当的分层,以确保代码架构的可维护性和可扩展性。
每周安装次数
128
仓库
GitHub 星标数
1
首次出现
2026年2月14日
安全审计
安装于
github-copilot118
codex115
opencode113
gemini-cli111
amp111
kimi-cli111
An architectural methodology skill for scaffolding and organizing frontend applications using Feature-Sliced Design principles.
Feature-Sliced Design v2.1 is a compilation of rules and conventions for organizing frontend code to make projects more understandable, maintainable, and stable in the face of changing business requirements.
Version 2.1 introduces the "Pages First" approach - keeping more code in pages and widgets rather than prematurely extracting it to features and entities.
The fundamental principle of FSD v2.1: Keep code where it's used until you need to reuse it.
Instead of immediately extracting everything into entities and features, start by keeping code in pages and widgets. Only move code to lower layers when you actually need to reuse it.
✅ Large UI blocks that are only used on one page ✅ Forms and their validation logic specific to a page ✅ Data fetching and state management for page-specific data ✅ Business logic that serves only this page/widget ✅ API interactions needed only here
FSD uses 6 active standardized layers organized by responsibility and dependencies. Layers are ordered from most specific (top) to most generic (bottom):
app/ ← Application initialization, providers, global styles
pages/ ← Full page compositions with their own logic, routing
widgets/ ← Large composite UI blocks with their own logic
features/ ← Reusable user interactions and business features
entities/ ← Reusable business entities (user, product, order)
shared/ ← Reusable infrastructure code (UI kit, utils, API)
Note : Historically, FSD included processes/ as a 7th layer, but it is deprecated in v2.1. If you're using it, move the code to features/ with help from app/ if needed.
Import Rule : A module can only import from layers strictly below it.
features/ → entities/, shared/pages/ → widgets/, features/, entities/, shared/entities/ → features/ (upward import)features/comments/ → (same-layer cross-import)Slices group code by business domain meaning. Each slice represents a specific business concept:
features/
├── auth/ ← Authentication feature
├── comments/ ← Comments functionality
└── post-editor/ ← Post editing feature
entities/
├── user/ ← User business entity
├── product/ ← Product business entity
└── order/ ← Order business entity
Key Rules :
Segments group code within slices by technical purpose :
features/
└── auth/
├── ui/ ← React components, styles, formatters
├── api/ ← API requests, data types, mappers
├── model/ ← State management, business logic, stores
├── lib/ ← Internal utilities for this slice
├── config/ ← Configuration, feature flags
└── index.ts ← Public API (exports only what other slices need)
Standard Segments :
ui - UI components, styles, date formattersapi - Backend interactions, request functions, data typesmodel - Data models, state stores, business logiclib - Utility functions needed by this sliceconfig - Configuration files, feature flagsEvery slice must define a public API through an index file:
// features/auth/index.ts
export { LoginForm } from './ui/LoginForm';
export { useAuth } from './model/useAuth';
export { loginUser } from './api/loginUser';
// Internal files not exported remain private to the slice
Rule : Modules outside a slice can only import from the public API , not from internal files.
New in v2.1 : You can now create explicit connections between slices on the same layer (typically entities) using the @x notation.
This allows entities to reference each other when there's a legitimate business relationship:
// entities/user/index.ts
export { UserCard } from './ui/UserCard';
export { userModel } from './model';
// entities/user/@x/order.ts
// Cross-import API specifically for the order entity
export { UserOrderHistory } from './ui/UserOrderHistory';
export { getUserOrders } from './api/getUserOrders';
// entities/order/index.ts
import { UserOrderHistory } from '@/entities/user/@x/order';
// Now order can import from user's cross-import API
When to use cross-imports :
Important : Regular cross-imports between slices (without @x) are still not allowed. Use @x notation to make cross-dependencies explicit and controlled.
Application-wide settings, providers, routing setup.
app/
├── providers/ ← Redux Provider, React Query, Theme Provider
├── styles/ ← Global CSS, resets, theme variables
├── index.tsx ← Application entry point
└── router.tsx ← Route configuration
Route-level compositions with their own logic and data management.
pages/
├── home/
│ ├── ui/
│ │ ├── HomePage.tsx
│ │ ├── HeroSection.tsx ← Large UI blocks
│ │ └── FeaturesGrid.tsx
│ ├── model/
│ │ └── useHomeData.ts ← Page-specific state
│ ├── api/
│ │ └── fetchHomeData.ts ← Page-specific API
│ └── index.ts
├── profile/
│ ├── ui/
│ │ ├── ProfilePage.tsx
│ │ ├── ProfileForm.tsx ← Forms specific to this page
│ │ └── ProfileStats.tsx
│ ├── model/
│ │ ├── profileStore.ts ← State for profile page
│ │ └── validation.ts ← Form validation
│ ├── api/
│ │ ├── updateProfile.ts
│ │ └── fetchProfile.ts
│ └── index.ts
└── settings/
v2.1 Approach : Pages can now contain:
Only extract to lower layers when you need to reuse the code elsewhere.
Complex, composite UI blocks with their own logic, used across multiple pages.
widgets/
├── header/
│ ├── ui/
│ │ ├── Header.tsx
│ │ ├── Navigation.tsx
│ │ └── UserMenu.tsx
│ ├── model/
│ │ └── headerStore.ts ← Widget state
│ ├── api/
│ │ └── fetchNotifications.ts ← Widget-specific API
│ └── index.ts
├── sidebar/
│ ├── ui/
│ ├── model/
│ │ └── sidebarState.ts
│ └── index.ts
└── footer/
v2.1 Approach : Widgets are no longer just compositional blocks. They can contain:
Only extract code to entities/features when other widgets or pages need it.
Reusable user interactions and complete business features used in multiple places.
features/
├── auth/
│ ├── ui/
│ │ ├── LoginForm.tsx
│ │ └── RegisterForm.tsx
│ ├── model/
│ │ └── useAuth.ts
│ ├── api/
│ │ ├── login.ts
│ │ └── register.ts
│ └── index.ts
├── add-to-cart/
├── like-post/
└── comment-create/
v2.1 Approach : Only create a feature when:
Don't create features prematurely. If a user interaction is only used in one place, keep it in the page or widget until you actually need to reuse it.
Reusable business entities - the core domain models used across the application.
entities/
├── user/
│ ├── ui/
│ │ ├── UserCard.tsx
│ │ └── UserAvatar.tsx
│ ├── model/
│ │ ├── types.ts
│ │ └── userStore.ts
│ ├── api/
│ │ └── userApi.ts
│ ├── @x/
│ │ └── order.ts ← Cross-import API for order
│ └── index.ts
├── product/
└── order/
v2.1 Approach : Only create an entity when:
Don't prematurely extract entities. If a data structure is only used in one place, keep it there until you need to share it.
Reusable infrastructure code with no business logic.
shared/
├── ui/ ← UI kit components
│ ├── Button/
│ ├── Input/
│ └── Modal/
├── lib/ ← Utilities
│ ├── formatDate/
│ ├── debounce/
│ └── classnames/
├── api/ ← API client setup, base config
│ ├── client.ts
│ └── apiRoutes.ts ← Route constants (v2.1: allowed!)
├── config/ ← Environment variables, constants
│ ├── env.ts
│ └── appConfig.ts
├── assets/ ← Images, fonts, icons
│ ├── logo.svg ← Company logo (v2.1: allowed!)
│ └── icons/
└── types/ ← Common TypeScript types
v2.1 Update : Shared can now contain application-aware code:
Still not allowed :
No slices in Shared - organized by segments only. Segments can import from each other within Shared.
Start simple - keep API logic in the page until you need to reuse it:
// pages/profile/api/fetchProfile.ts
export const fetchProfile = (id: string) =>
apiClient.get(`/users/${id}`);
// pages/profile/model/useProfile.ts
export const useProfile = (id: string) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchProfile(id).then(setUser);
}, [id]);
return user;
};
// pages/profile/ui/ProfilePage.tsx
import { useProfile } from '../model/useProfile';
const ProfilePage = () => {
const user = useProfile('123');
return <div>{user?.name}</div>;
};
Only move to entities when other pages need the same API:
// entities/user/api/userApi.ts
export const fetchUser = (id: string) =>
apiClient.get(`/users/${id}`);
// entities/user/model/userStore.ts
export const useUser = (id: string) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(id).then(setUser);
}, [id]);
return user;
};
// entities/user/index.ts
export { useUser } from './model/userStore';
export type { User } from './model/types';
// Now multiple pages can use it:
// pages/profile/ui/ProfilePage.tsx
// pages/user-list/ui/UserListPage.tsx
import { useUser } from '@/entities/user';
Features can use entities and other features:
// features/post-card/ui/PostCard.tsx
import { UserAvatar } from '@/entities/user';
import { LikeButton } from '@/features/like-post';
import { CommentButton } from '@/features/comment-create';
export const PostCard = ({ post }) => (
<article>
<UserAvatar userId={post.authorId} />
<h2>{post.title}</h2>
<p>{post.content}</p>
<div>
<LikeButton postId={post.id} />
<CommentButton postId={post.id} />
</div>
</article>
);
Routes should be defined in the App layer, pages composed in Pages layer:
// app/router.tsx
import { HomePage } from '@/pages/home';
import { ProfilePage } from '@/pages/profile';
export const router = createBrowserRouter([
{ path: '/', element: <HomePage /> },
{ path: '/profile/:id', element: <ProfilePage /> },
]);
// app/index.tsx
import { RouterProvider } from 'react-router-dom';
import { router } from './router';
export const App = () => <RouterProvider router={router} />;
// shared/ui/Button/Button.tsx
export const Button = ({ children, onClick, variant = 'primary' }) => (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
);
// shared/ui/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
// Usage in feature
import { Button } from '@/shared/ui/Button';
const LoginForm = () => (
<form>
<Button variant="primary">Login</Button>
</form>
);
Use path aliases for clean imports:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/app/*": ["src/app/*"],
"@/pages/*": ["src/pages/*"],
"@/widgets/*": ["src/widgets/*"],
"@/features/*": ["src/features/*"],
"@/entities/*": ["src/entities/*"],
"@/shared/*": ["src/shared/*"]
}
}
}
Start with: "Where is this code used?"
Is it reusable infrastructure?
Is it a complete user action?
Is it a business domain concept?
Is it app-wide setup?
"User profile form with validation"
pages/profile/ui/ProfileForm.tsxfeatures/profile-form/"Product card component"
pages/products/ui/ProductCard.tsxwidgets/product-card/ or entities/product/ui/ProductCard.tsxshared/ui/Card/"Fetch product data"
pages/product-detail/api/fetchProduct.tsentities/product/api/productApi.ts"Modal manager"
shared/ui/modal-manager/"When in doubt, keep it in pages/widgets. Extract to lower layers when you actually need to reuse it."
Don't try to predict reusability. Wait for actual reuse to emerge, then refactor.
❌ Premature extraction (v2.1 key anti-pattern)
// Immediately creating an entity/feature before knowing if it's needed
// entities/user-profile-form/ ← Used only on one page!
✅ Solution : Keep in page until actually needed elsewhere
// pages/profile/ui/ProfileForm.tsx ← Start here
// Only move to features/ when another page needs it
❌ Cross-imports between slices on same layer
// features/comments/ui/CommentList.tsx
import { likePost } from '@/features/like-post'; // BAD!
✅ Solution : Use @x notation for entities, or compose at higher layer
// For entities with business relationships:
// entities/user/@x/order.ts
export { UserOrderHistory } from './ui/UserOrderHistory';
// For features, compose at page level:
// pages/post/ui/PostPage.tsx
import { CommentList } from '@/features/comments';
import { LikeButton } from '@/features/like-post';
❌ Business logic in Shared
// shared/lib/userHelpers.ts
export const calculateUserReputation = (user) => { ... }; // BAD!
✅ Solution : Move to entities layer
// entities/user/lib/calculateReputation.ts
export const calculateUserReputation = (user) => { ... };
❌ Bypassing public API
import { LoginButton } from '@/features/auth/ui/LoginButton'; // BAD!
✅ Use public API
import { LoginButton } from '@/features/auth'; // GOOD!
❌ God slices (too much responsibility)
// features/user-management/ ← TOO BROAD
// - login, register, profile-edit, password-reset, etc.
✅ Split into focused features
// features/auth/
// features/profile-edit/
// features/password-reset/
If you have an existing FSD 2.0 project, migration to 2.1 is non-breaking. You can adopt the "pages first" approach gradually.
Audit current features and entities
Move page-specific code back to pages
pages/[page]/ui/pages/[page]/api/pages/[page]/model/Move widget-specific code to widgets
Keep truly reusable code in features/entities
Update Shared with application-aware code
shared/api/routes.tsBefore (v2.0):
features/
└── user-profile-form/ ← Only used on profile page
├── ui/
├── model/
└── api/
pages/
└── profile/
└── ui/
└── ProfilePage.tsx ← Just composition
After (v2.1):
pages/
└── profile/
├── ui/
│ ├── ProfilePage.tsx
│ └── ProfileForm.tsx ← Moved here
├── model/
│ └── profileStore.ts ← Moved here
└── api/
└── updateProfile.ts ← Moved here
When migrating existing code to FSD:
shared/entities/features/app/Do it gradually - you don't need to refactor everything at once.
features/
└── todo-list/
├── ui/
│ └── TodoList.tsx
├── model/
│ ├── todoSlice.ts ← Redux slice
│ ├── selectors.ts ← Selectors
│ └── thunks.ts ← Async actions
└── index.ts
entities/
└── user/
├── ui/
├── api/
│ └── userQueries.ts ← React Query hooks
├── model/
│ └── types.ts
└── index.ts
Place FSD structure in src/ folder to avoid conflicts with Next.js app/ or pages/ folders:
my-nextjs-project/
├── app/ ← Next.js App Router (if using)
├── pages/ ← Next.js Pages Router (if using)
└── src/
├── app/ ← FSD app layer
├── pages/ ← FSD pages layer
├── widgets/
├── features/
├── entities/
└── shared/
project/
├── src/
│ ├── app/
│ ├── pages/
│ ├── widgets/
│ ├── features/
│ ├── entities/
│ └── shared/
├── tsconfig.json
├── vite.config.ts
└── package.json
project/
└── src/
├── app/
├── pages/
├── widgets/
├── features/
├── entities/
└── shared/
Layer Selection :
app/pages/widgets/features/entities/shared/Import Direction : App → Pages → Widgets → Features → Entities → Shared
Public API : Always create index.ts for slices to export public interface
For more detailed information and edge cases:
When implementing FSD in a project:
Steiger is an official linter that helps enforce FSD rules automatically:
Installation:
npm install -D @feature-sliced/steiger
Usage:
npx steiger src
Steiger is production-ready and actively maintained. It's the best way to ensure your team follows FSD conventions consistently.
Trigger this skill when:
"Start simple, extract when needed."
Don't try to predict the future architecture. Build features in pages and widgets first. When you see actual reuse patterns emerging, then extract to features and entities. This leads to:
This skill provides the foundational knowledge to structure any frontend application using Feature-Sliced Design v2.1 methodology. Always prioritize code cohesion, wait for actual reuse before extracting, and maintain proper layering to ensure maintainable and scalable code architecture.
Weekly Installs
128
Repository
GitHub Stars
1
First Seen
Feb 14, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
github-copilot118
codex115
opencode113
gemini-cli111
amp111
kimi-cli111
任务估算指南:敏捷开发故事点、计划扑克、T恤尺码法详解
10,500 周安装
PowerShell 7 专家指南:跨平台自动化、并行处理与REST API集成
167 周安装
用户画像创建指南:AI辅助工具与Python代码示例,提升产品设计与营销效果
167 周安装
GitHub PR自动生成工具 - 智能拉取请求摘要生成器,提升开发团队协作效率
167 周安装
SwiftUI设计原则:间距、排版与原生组件使用指南,打造专业iOS应用
167 周安装
agent-eval:编程智能体评估工具,自动化测试比较AI代码助手性能
167 周安装
Encore.ts 声明式基础设施:PostgreSQL、Redis、Pub/Sub、定时任务、对象存储
167 周安装
features/posts/shared/assets/Deprecate Processes layer
Consider using @x notation