Feature-Sliced Design для React проектов: когда FSD помогает, а когда усложняет код

Практический разбор Feature-Sliced Design для React проектов: слои, public API, правила зависимостей, типичные ошибки, производительность и ответы для интервью.

24 марта 2026 г.19 минLexicon Team

Введение

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 пытается решить это не переименованием папок, а системой ограничений:

  1. Код делится на слои ответственности.
  2. Внутри слоев код группируется по бизнес-срезам, а не только по типам файлов.
  3. У каждого модуля есть public API, через который с ним можно работать снаружи.
  4. Направление зависимостей фиксировано: верхние слои собирают нижние, но не наоборот.

Практический эффект здесь важнее теории. Если модуль можно поменять без чтения половины проекта, архитектура помогает. Если FSD внедрили, а импортов «через внутренности» стало еще больше, значит, в проекте скопировали названия слоев, но не договоренности.

Базовая модель FSD: из каких слоев состоит проект

Классическая схема Feature-Sliced Design для React обычно выглядит так:

  • app
  • pages
  • widgets
  • features
  • entities
  • shared

Иногда в старых примерах встречается 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.

Поток данных и управления

На странице заказов событие часто идет так:

  1. Пользователь меняет фильтр.
  2. features/orders-filters обновляет URL или локальную модель фильтров.
  3. Виджет таблицы запрашивает список на основе актуальных параметров.
  4. entities/order поставляет типы, адаптеры и базовый UI для заказа.
  5. features/change-order-status запускает мутацию и обновляет нужный экранный фрагмент.
  6. 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 обычно строится так:

  1. FSD это подход к архитектуре фронтенда, где код делят по слоям ответственности и бизнес-срезам, а не только по типам файлов.
  2. Основная цель не в красоте папок, а в контроле зависимостей и локализации изменений.
  3. Базовые слои обычно такие: app, pages, widgets, features, entities, shared.
  4. Entity описывает доменную сущность, а feature пользовательское действие вокруг этой сущности.
  5. Критически важен 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

Читайте также