Feature-Sliced Design для React проектов: когда FSD помогает, а когда усложняет код
Практический разбор Feature-Sliced Design для React проектов: слои, public API, правила зависимостей, типичные ошибки, производительность и ответы для интервью.
- Введение
- Какую проблему решает Feature-Sliced Design
- Базовая модель FSD: из каких слоев состоит проект
- Что означает каждый слой
- Архитектурный разбор: как FSD выглядит на реальной странице
- Контекст задачи
- Возможная схема модулей
- Поток данных и управления
- Public API модулей: почему без него FSD не работает
- Где в FSD должно жить состояние
- Сравнение FSD с другими подходами
- Production pitfalls
- 1. Слой shared снова стал свалкой
- 2. Команда путает feature и entity
- 3. Pages знают слишком много
- 4. Public API есть, но им не пользуются
- Разбор производительности: помогает ли FSD быстрее рендерить
- Практики, которые делают FSD рабочим, а не декоративным
- Сначала договоритесь о языке
- Держите модули маленькими по ответственности, а не по числу файлов
- Проверяйте правила зависимостей автоматически
- Не внедряйте FSD целиком за один раз
- Не путайте «универсально» и «повторно используется»
- Частые ошибки
- Как отвечать на интервью
- FAQ
- Когда FSD действительно нужен в React-проекте?
- Чем entity отличается от feature?
- Можно ли использовать FSD без Redux, Zustand или MobX?
- Что будет, если не соблюдать public API в FSD?
- FSD улучшает производительность приложения?
- Итоги
Введение
Feature-Sliced Design, или FSD, часто упоминают как «архитектуру для больших React-проектов». Формулировка удобная, но слишком расплывчатая. На практике FSD нужен не ради красивой схемы папок, а ради трёх приземлённых вещей: ограничения хаотичных зависимостей, раскладки кода по бизнес-смыслам и локализации изменений. Когда приложение растет, это становится важнее, чем выбор конкретного store или UI-библиотеки.
Если уже приходилось спорить, где должна жить логика фильтров, почему shared внезапно знает про заказы и почему одна фича импортирует внутренности другой, то проблема уже архитектурная. В таком контексте FSD полезно рассматривать вместе с материалом про React архитектуру больших приложений: там хорошо видно, почему одной группировки по components, hooks и utils хватает только до определенного масштаба.
В этой статье разберем, что именно решает Feature-Sliced Design, как устроены его слои, где команды чаще всего ошибаются, как FSD сочетается с состоянием и производительностью, и как объяснять этот подход на собеседовании без шаблонных формулировок.
Больше вопросов в Telegram
Ежедневные разборы и реальные кейсы с интервью.
Какую проблему решает Feature-Sliced Design
У среднего React-проекта хаос начинается не с тысячи файлов, а с размытых границ. В кодовой базе быстро появляются типовые симптомы:
- папка
sharedпревращается в склад доменной логики; pagesразрастаются до файлов на 500-800 строк;- одна фича тянет хуки, селекторы и компоненты другой напрямую;
- повторяющиеся сценарии лежат в трех местах под разными именами;
- новый разработчик не может предсказать, где искать код изменения.
FSD пытается решить это не переименованием папок, а системой ограничений:
- Код делится на слои ответственности.
- Внутри слоев код группируется по бизнес-срезам, а не только по типам файлов.
- У каждого модуля есть
public API, через который с ним можно работать снаружи. - Направление зависимостей фиксировано: верхние слои собирают нижние, но не наоборот.
Практический эффект здесь важнее теории. Если модуль можно поменять без чтения половины проекта, архитектура помогает. Если FSD внедрили, а импортов «через внутренности» стало еще больше, значит, в проекте скопировали названия слоев, но не договоренности.
Базовая модель FSD: из каких слоев состоит проект
Классическая схема Feature-Sliced Design для React обычно выглядит так:
apppageswidgetsfeaturesentitiesshared
Иногда в старых примерах встречается processes, но в большинстве современных проектов основная рабочая рамка строится вокруг шести слоев выше.
Что означает каждый слой
app держит точку входа, провайдеры, роутинг, инициализацию приложения, глобальные границы и инфраструктуру уровня оболочки. Здесь не должна жить бизнес-логика конкретной фичи.
pages собирают экран маршрута. Это не место для глубокой доменной логики, но именно здесь понятно, какие виджеты и сценарии формируют страницу.
widgets нужны для крупных самостоятельных UI-блоков, которые объединяют несколько сущностей и фич в осмысленный фрагмент интерфейса: хедер каталога, таблица заказов, профиль пользователя, боковая панель аналитики.
features описывают пользовательское действие. Не «таблица» и не «карточка», а «добавить в корзину», «сменить пароль», «отфильтровать список», «подписаться на уведомления».
entities описывают устойчивые доменные сущности: user, product, order, invoice. Здесь обычно живут типы, базовые селекторы, компактные UI-представления сущности и связанные доменные адаптеры.
shared содержит инфраструктурные примитивы: UI-kit, API-клиент, конфиг, роутинг-утилиты, общие хуки, функции форматирования, которые не привязаны к предметной области.
Ключевая мысль FSD: важны не сами слова widgets и features, а то, что они задают границы ответственности. Если слой shared знает о скидках и статусах заказов, это уже не shared. Если feature напрямую зависит от другой feature, граница действий сломана.
Архитектурный разбор: как FSD выглядит на реальной странице
Контекст задачи
Представим B2B-экран со списком заказов: есть фильтры, таблица, карточка деталей, смена статуса, оптимистичное обновление и права доступа по ролям. Это хороший пример, потому что здесь быстро видно, где проходит граница между сущностью, фичей и виджетом.
Возможная схема модулей
src/
app/
providers/
router/
pages/
orders/
ui/OrdersPage.tsx
widgets/
orders-table/
ui/OrdersTableWidget.tsx
order-details/
ui/OrderDetailsWidget.tsx
features/
change-order-status/
model/
ui/
index.ts
orders-filters/
model/
ui/
index.ts
entities/
order/
model/
ui/
api/
index.ts
shared/
api/
ui/
lib/
Такое дерево полезно не само по себе. Оно отвечает на вопрос: где именно должен лежать код.
- Если меняется формат отображения заказа, это обычно
entities/order. - Если пользователь применяет фильтр, сценарий лежит в
features/orders-filters. - Если экран собирает таблицу, фильтры и сайдбар, точка композиции находится в
widgetsиpages.
Поток данных и управления
На странице заказов событие часто идет так:
- Пользователь меняет фильтр.
features/orders-filtersобновляет URL или локальную модель фильтров.- Виджет таблицы запрашивает список на основе актуальных параметров.
entities/orderпоставляет типы, адаптеры и базовый UI для заказа.features/change-order-statusзапускает мутацию и обновляет нужный экранный фрагмент.pages/ordersтолько собирает эти части, но не знает внутренних деталей реализации каждого сценария.
Именно на этом уровне FSD окупается. Он не убирает сложность предметной области, но распределяет ее по понятным зонам ответственности. Если вместо этого вся логика уходит в pages/orders.tsx, команда снова получает монолит, только в папке с модным названием.
Public API модулей: почему без него FSD не работает
Одна из самых недооцененных идей Feature-Sliced Design это - public API. Команда часто копирует слои, но продолжает импортировать код по длинным путям импорта:
import { getOrderLabel } from "@/entities/order/model/selectors/getOrderLabel";
import { ChangeOrderStatusButton } from "@/features/change-order-status/ui/button/ChangeOrderStatusButton";
Формально структура есть. Фактически внешний код знает внутреннее устройство модуля, и любая перестройка папок ломает полпроекта.
Рабочий вариант выглядит так:
import { OrderCard, type Order } from "@/entities/order";
import { ChangeOrderStatusButton } from "@/features/change-order-status";
А сам модуль экспортирует наружу только то, что действительно является контрактом:
// entities/order/index.ts
export { OrderCard } from "./ui/OrderCard";
export type { Order } from "./model/types";
export { formatOrderStatus } from "./lib/formatOrderStatus";
Что это дает в реальном проекте:
- можно реорганизовать внутренности модуля без массовых правок импортов;
- соседние слои меньше знают о деталях реализации;
- архитектурные правила проще проверять линтерами и code review;
- модуль легче воспринимается как единица ответственности, а не как набор случайных файлов.
Если говорить жестко, то без public API FSD превращается в косметику. Слои остаются на месте, но зависимости продолжают течь через любые дыры.
Где в FSD должно жить состояние
Одна из частых ошибок при внедрении FSD заключается в том, чтобы сначала разложить папки, а уже потом думать, где должен жить state. Наоборот, лучше работает другой порядок: сначала разобраться с состоянием, а потом уже определять структуру папок. Сначала полезно разделить типы состояния:
- локальное UI-состояние;
- состояние пользовательского сценария;
- серверное состояние;
- URL-состояние;
- инфраструктурное состояние приложения.
Локальное UI-состояние обычно остается рядом с компонентом или внутри конкретной feature. Не нужно выносить isModalOpen в глобальную модель только ради чистоты схемы.
Состояние пользовательского действия часто живет в features. Например, логика смены пароля, подтверждения удаления или управления фильтрами хорошо чувствует себя именно там.
Серверное состояние не обязано попадать в entities только потому, что связано с сущностью. entities описывает доменную модель, но сам кэш запросов и стратегия инвалидации могут жить в отдельном data-fetching слое модуля. Эту границу удобно держать в голове вместе с полным разбором state management в React.
Context в FSD тоже не запрещен, но его стоит использовать по назначению. Для инфраструктурных данных или локальных поддеревьев он подходит, а для горячего разделяемого состояния по всему приложению часто нужны более селективные подписки. На этом месте хорошо помогает материал про когда использовать React Context API.
Короткое правило: FSD не отвечает за все решения, связанные с управлением состоянием, автоматически. Он помогает понять, где проходит граница модуля. А уже внутри модуля вы выбираете useState, Context, query cache или store по характеру состояния.
Сравнение FSD с другими подходами
| Подход | Сильная сторона | Ограничение | Когда уместен | Где начинает болеть |
|---|---|---|---|---|
components/hooks/utils | Очень низкий порог входа | Почти нет архитектурных границ | Маленькие проекты и прототипы | При росте логики непонятно, где живет бизнес-смысл |
| Структура по страницам | Удобна на раннем этапе | Общий код быстро размывает страницы | Несколько независимых экранов | Логика начинает дублироваться между route-модулями |
| Папки только по feature | Хорошо группирует сценарии | Нет слоя для сущностей и инфраструктуры | Продукт со средним масштабом | Трудно отделить доменную модель от пользовательских действий |
| FSD | Явные слои, зависимости и public API | Требует дисциплины и онбординга команды | Средние и большие React-проекты | При формальном внедрении превращается в церемонию |
| Микрофронтенды | Организационная независимость команд | Высокая цена интеграции | Очень большие организации и домены | Для обычного продукта это часто преждевременное усложнение |
У FSD нет магического преимущества над всеми альтернативами. Его сила в том, что он вводит язык для обсуждения архитектуры: что такое сущность, где заканчивается действие пользователя, почему конкретный код не должен попадать в shared. Для команд из двух человек на шесть экранов этот язык может принести больше вреда, чем пользы. Для большого продукта он, наоборот, экономит месяцы на поддержке.
Production pitfalls
1. Слой shared снова стал свалкой
Симптомы:
- в
sharedпоявляютсяuserHelpers,orderUtils,checkoutConfig; - нейтральным именем маскируют доменную логику;
- слой начинает зависеть от бизнес-типов.
Последствие: модульная граница исчезает, и проект возвращается к старой проблеме под новым названием.
Исправление: все, что знает о домене, должно подниматься в entities, features или widgets, а не прятаться в shared.
2. Команда путает feature и entity
Типичная ошибка: карточку пользователя, редактирование пользователя и фильтрацию пользователей кладут в одну папку user, потому что «это все про пользователя».
Проблема в том, что entity это устойчивый объект предметной области, а feature это действие. Если их объединить, модуль быстро становится внутренним монолитом.
3. Pages знают слишком много
Симптомы:
- в
pagesлежат запросы, маппинг данных, бизнес-ветвления, мутации и локальные адаптеры; - экран трудно тестировать и читать;
- любой рефакторинг тянет огромный diff.
Исправление: страница должна быть слоем композиции. Если в ней оседает предметная логика, часть кода требует выноса на нижние слои.
4. Public API есть, но им не пользуются
В проекте может быть настроен index.ts, но разработчики все равно импортируют внутренние селекторы и компоненты через глубокие пути. Через несколько месяцев правило перестает существовать.
Исправление простое, но дисциплинарное: линтеры, review и запрет прямых импортов из внутренних директорий.
Разбор производительности: помогает ли FSD быстрее рендерить
Сам по себе Feature-Sliced Design не делает React-приложение быстрее. Он не уменьшает bundle магически и не убирает ререндеры по факту существования слоя features. Но FSD косвенно влияет на производительность в двух важных местах.
Во-первых, он помогает локализовать состояние и обновления. Когда пользовательский сценарий живет в отдельной фиче, проще не тащить его флаги через весь экран. Это снижает шанс того, что открытие модалки заставит пересобраться половину страницы.
Во-вторых, FSD упрощает точечную оптимизацию. Если модуль имеет понятный public API, его легче лениво подгрузить, профилировать или заменить без каскада зависимостей. На этом уровне архитектура стыкуется с темами code splitting и lazy loading в React и профилирования React-приложения.
Однако есть и обратная сторона медали. Плохое внедрение FSD может ухудшить ситуацию:
- слишком мелкие модули размазывают логику и усложняют рендер-поток;
- слой
widgetsпревращается в прослойку ради прослойки; - обилие адаптеров и оберток делает код многословным без реальной изоляции;
- команда начинает оптимизировать папки вместо узких мест интерфейса.
Когда FSD помогает производительности:
- состояние локализовано по сценариям;
- границы модулей совпадают с границами UI и данных;
- тяжелые виджеты можно изолировать и профилировать отдельно.
Когда FSD не спасет:
- серверный кэш дублируется в нескольких местах;
- Context слишком широкий;
- горячие вычисления висят в рендере таблицы на тысячи строк;
- проект не профилируется, а проблемы ищут «по ощущению».
Прокачай React за 7 дней
20 вопросов и разборов по React Hooks.
Практики, которые делают FSD рабочим, а не декоративным
Сначала договоритесь о языке
Команда должна одинаково понимать, что такое entity, feature и widget. Если у каждого своя трактовка, архитектура развалится на первом же спринте.
Держите модули маленькими по ответственности, а не по числу файлов
Хороший модуль не обязан быть маленьким. Он должен делать одну осмысленную вещь. Иногда одна фича содержит и модель, и UI, и API-слой, и это нормально, если граница у нее ясная.
Проверяйте правила зависимостей автоматически
Архитектурные договоренности плохо живут только на словах. Их нужно проверять автоматически: запрет глубоких импортов, запрет зависимостей снизу вверх, контроль public API.
Не внедряйте FSD целиком за один раз
Обычно лучше взять самый проблемный экран, выделить несколько реальных сущностей и фич, настроить public API, проверить удобство команды и только потом масштабировать подход. Большой одномоментный рефакторинг часто заканчивается усталостью, а не улучшением архитектуры.
Не путайте «универсально» и «повторно используется»
Если модуль используется в трех местах, это еще не делает его shared. Для shared важно отсутствие предметной привязки. Это правило часто нарушают в React-кодовых базах уже на втором месяце проекта.
// Плохо: shared знает о домене order
import { OrderStatusBadge } from "@/shared/ui/OrderStatusBadge";
// Лучше: доменный элемент лежит рядом с сущностью
import { OrderStatusBadge } from "@/entities/order";
Частые ошибки
- Внедрять FSD в маленький проект без реальных архитектурных проблем.
- Считать, что FSD это просто новая схема каталогов.
- Складывать доменную логику в
shared, чтобы не спорить о границах. - Делать
widgetsдля каждого блока интерфейса подряд, даже когда достаточноentityилиfeature. - Импортировать внутренности модуля в обход
public API. - Ожидать, что FSD сам решит вопросы state management, кэширования и производительности.
Как отвечать на интервью
Убедительный ответ на вопрос про Feature-Sliced Design обычно строится так:
- FSD это подход к архитектуре фронтенда, где код делят по слоям ответственности и бизнес-срезам, а не только по типам файлов.
- Основная цель не в красоте папок, а в контроле зависимостей и локализации изменений.
- Базовые слои обычно такие:
app,pages,widgets,features,entities,shared. Entityописывает доменную сущность, аfeatureпользовательское действие вокруг этой сущности.- Критически важен
public APIмодулей, иначе внешние слои начинают импортировать внутренности и архитектура теряет смысл.
Если хотите показать уровень выше пересказа терминов, добавьте компромисс:
Feature-Sliced Design не нужен каждому проекту. Для маленького приложения он может быть дороже пользы. Но в растущем React-продукте с несколькими командами FSD помогает сделать зависимости предсказуемыми и упростить поддержку.
Полезно еще проговорить, что FSD не заменяет здравый выбор состояния. Например, локальный UI-state можно оставить внутри фичи, а серверное состояние не стоит слепо тащить в глобальный store. Такой ответ звучит заметно сильнее, чем фраза «это современная архитектура для фронтенда».
Потренируйте React-архитектуру и FSD на реальных собеседованиях
Практика по React с разбором границ модулей, state management, public API, performance и архитектурных компромиссов в формате технического интервью.
FAQ
Когда FSD действительно нужен в React-проекте?
Когда кодовая база перестаёт удерживаться в сознании одной команды: появляются несколько доменов, повторяющиеся пользовательские сценарии, частые конфликты зависимостей и необходимость безопасно менять отдельные модули без каскадных эффектов.
Чем entity отличается от feature?
Entity описывает устойчивую доменную сущность, например user или order. Feature описывает действие пользователя: изменить статус заказа, войти в систему, применить фильтр. Это два разных уровня ответственности.
Можно ли использовать FSD без Redux, Zustand или MobX?
Да. FSD не диктует библиотеку состояния. Он определяет структуру модулей и границы зависимостей, а конкретный механизм состояния выбирают по задаче.
Что будет, если не соблюдать public API в FSD?
Проект очень быстро начнёт импортировать внутренние файлы напрямую. После этого любая перестройка модуля превращается в массовый рефакторинг, а слои остаются только на диаграмме.
FSD улучшает производительность приложения?
Не напрямую. Он помогает локализовать изменения, изолировать состояние и упрощает точечную оптимизацию. Но если проект страдает от тяжелых ререндеров или неверной модели данных, одна только структура папок проблему не решит.
Итоги
Feature-Sliced Design полезен там, где React-проект уже упирается не в синтаксис компонентов, а в сложность изменений. Сильная сторона подхода не в наборе терминов, а в дисциплине: явные слои, понятные модули, фиксированное направление зависимостей и public API, который защищает внутреннее устройство кода.
При этом FSD не стоит романтизировать. Он не нужен для каждого лендинга и не заменяет инженерные решения по данным, состоянию и производительности. Но если кодовая база растет, а команда хочет уменьшить архитектурный шум, FSD дает очень полезную рамку. По этой причине на собеседовании убедительнее звучит не «я знаю слои FSD», а «я понимаю, какую проблему они решают и где их границы действительно помогают».
Больше вопросов в Telegram
Ежедневные разборы и реальные кейсы с интервью.
Автор
Lexicon Team
Читайте также
frontend
System design для frontend разработчика: как мыслить системно, а не только компонентами
Практический разбор system design для frontend разработчика: границы ответственности, данные, производительность, ошибки и сильный ответ на интервью.
frontend
React архитектура больших приложений: как не утонуть в слоях, состояниях и фичах
Практический разбор React архитектуры больших приложений: слои, feature-модули, state management, performance, ошибки и критерии выбора.
frontend
React patterns, которые спрашивают на senior интервью: не определения, а архитектурные компромиссы
Разбираем React patterns для senior интервью: HOC, Render Props, Compound Components, controlled/uncontrolled, headless API и критерии выбора.