Раскладка по фичам — хорошее начало: код одной задачи лежит в одной папке. Но когда фич становится не пять, а сорок, а команда — не один человек, а несколько, простого деления на features/ и shared/ уже мало. Возникают вопросы, на которые «по фичам» не отвечает: может ли одна фича импортировать другую? Где живёт сущность «Товар», которую используют десять фич? Куда положить шапку сайта, которая собирает в себя несколько фич сразу?
Feature-Sliced Design (FSD) — это методология, которая даёт на эти вопросы одинаковые ответы для всей команды. Она добавляет к идее «по фичам» два уровня порядка: слои (кто на кого может ссылаться) и сегменты (что где лежит внутри). Дальше — по шагам.
Три оси: слои, слайсы, сегменты
FSD режет проект по трём осям сразу.
- Слои — верхний уровень. Их фиксированный набор, и у них есть иерархия: верхние слои могут использовать нижние, но не наоборот.
- Слайсы — деление внутри слоя по домену:
entities/user,entities/product,features/add-to-cart. Имена слайсов придумываете вы, под свою предметную область. - Сегменты — деление внутри слайса по технической роли:
ui/,model/,api/,lib/.
Путь до файла читается как адрес: features/add-to-cart/ui/AddButton.tsx — «фича добавления в корзину, её визуальная часть, кнопка». По одному пути понятно, что это и на каком уровне живёт.
Слои — снизу вверх
Слоёв шесть. Разберём от самого нижнего (ни от кого не зависит) к верхнему (собирает всё вместе).
app ← точка сборки: роутинг, провайдеры, глобальные стили
pages ← страницы: собирают виджеты и фичи под конкретный URL
widgets ← крупные самостоятельные блоки (шапка, карточка товара)
features ← действия пользователя (добавить в корзину, оставить отзыв)
entities ← бизнес-сущности (Товар, Пользователь, Заказ)
shared ← общее без привязки к домену (UI-кит, хелперы, конфиг)
shared/— то, что не знает ничего о вашем бизнесе: примитивные кнопки и поля, обёртки над сетью, форматтеры дат. Отсюда берут все.entities/— бизнес-сущности: как выглядитТовар, его типы, запрос за одним товаром, карточка для отображения. Сущность ничего не «делает» — она про данные и их показ.features/— действия над сущностями: «добавить в корзину», «оставить отзыв», «применить промокод». Фича — это законченный кусочек ценности для пользователя.widgets/— крупные блоки, которые склеивают несколько фич и сущностей в самостоятельный виджет: шапка сайта, боковая панель, большая карточка товара с кнопкой покупки.pages/— страницы под конкретный маршрут: собирают виджеты и фичи в то, что видит пользователь по адресу/product/42.app/— единственная точка сборки: роутер, провайдеры, глобальные стили, инициализация. Всё приложение стартует отсюда.
Слайсы — деление по домену
Внутри entities/, features/, widgets/, pages/ код делят на слайсы — по предметной области:
entities/
product/ ← всё про сущность «Товар»
user/ ← всё про «Пользователя»
features/
add-to-cart/ ← фича «добавить в корзину»
leave-review/ ← фича «оставить отзыв»
Слайс — это замкнутый мирок одной темы. Правило простое: слайс не лезет во внутренности соседнего слайса того же слоя. Фича add-to-cart не импортирует напрямую из фичи leave-review. Если им нужно что-то общее — это общее спускается на слой ниже (в entities или shared).
Сегменты — деление по роли
Внутри слайса код раскладывают по сегментам — техническим ролям:
features/add-to-cart/
ui/ ← компоненты (кнопка, форма)
model/ ← состояние, логика, хуки
api/ ← запросы к серверу
lib/ ← вспомогательные функции слайса
index.ts ← публичный интерфейс слайса
Сегменты необязательны все сразу: у простой фичи может быть только ui/ и model/. Важно, что роль файла видна по папке, а не по догадке.
Правило импортов — главное в FSD
Вся строгость FSD держится на одном правиле:
Слой может импортировать только из слоёв ниже себя.
features берёт из entities и shared, но не из widgets или pages. entities берёт только из shared. shared не берёт ни у кого. Это делает зависимости однонаправленными: нижние слои не знают о верхних, поэтому их легко переиспользовать и тестировать, а изменение страницы не задевает сущность.
Второе правило — публичный интерфейс. У каждого слайса есть index.ts, через который наружу торчит только то, что можно использовать. Остальное — внутренняя кухня слайса:
// features/add-to-cart/index.ts
export { AddToCartButton } from './ui/AddToCartButton'
// model, api, lib наружу не выставлены — это детали реализации
Импортируют всегда из слайса целиком, а не из его файлов напрямую:
import { AddToCartButton } from 'features/add-to-cart' // да
import { AddToCartButton } from 'features/add-to-cart/ui/AddToCartButton' // нет
Оба правила несложно проверять автоматически — для FSD есть ESLint-плагин, который не даст импортировать «вверх» или мимо публичного интерфейса.
Небольшой пример
Кнопка «Купить» на странице товара маркетплейса разложится так:
entities/product— типProduct, запросgetProduct(id), компонентProductCardдля отображения.features/add-to-cart— кнопкаAddToCartButtonи логика добавления; использует типProductизentities.widgets/product-panel— большая панель:ProductCardизentities+AddToCartButtonизfeatures.pages/product— страница/product/:id, которая ставит на местоproduct-panelи подгружает данные.app— роутер связывает/product/:idс этой страницей.
Зависимости идут строго вниз: страница знает о виджете, виджет — о фиче и сущности, фича — о сущности, сущность — только о shared. Обратных стрелок нет.
Когда FSD оправдан, а когда нет
FSD — это дисциплина, и как у любой дисциплины, у неё есть цена: больше папок и церемоний на старте.
- Оправдан, когда приложение большое и живёт долго, над ним работает несколько человек, фичи пересекаются и переиспользуются. Тогда единые правила экономят часы споров «куда это положить» и предотвращают клубок зависимостей.
- Избыточен для маленького приложения на пару экранов или прототипа. Там простой раскладки по фичам достаточно, а шесть слоёв только мешают.
Хорошая новость: переход плавный. Можно начать с shared / entities / features и добавить widgets / pages, когда экранов станет много. FSD не требует всё сразу.
Коротко
- FSD режет проект по трём осям: слои (иерархия), слайсы (домены), сегменты (роли:
ui/model/api/lib). - Шесть слоёв снизу вверх:
shared → entities → features → widgets → pages → app. - Главное правило: импортировать можно только из слоёв ниже — зависимости однонаправленные.
- Каждый слайс отдаёт наружу только
index.ts(публичный интерфейс); внутренности скрыты. - Слайсы одного слоя не лезут друг в друга — общее спускают на слой ниже.
- FSD оправдан для крупных долгоживущих приложений; для маленьких хватит раскладки по фичам.
Что почитать дальше
- Структура React+TypeScript-проекта — раскладка по фичам, с которой начинается путь к FSD.
- Архитектурные подходы фронтенда — где FSD среди других способов организовать код.
- Состояние — где в слоях FSD живёт состояние и когда оно локальное, а когда общее.
- Компоненты — как строить компоненты с правильными границами внутри слайсов.