Недостатки Feature Sliced Design - подробный разбор проблем и подводных камней

19 февраля 2026
Автор

Олег Марков

Введение

Feature-Sliced Design (FSD) часто подается как современный и удобный подход к архитектуре фронтенда, особенно в React-проектах. Он обещает масштабируемость, модульность, независимое развитие фич, удобную навигацию по коду. Но любая архитектура имеет стоимость и ограничения, и FSD не исключение.

В этой статье вы увидите, какие реальные проблемы и недостатки возникают при использовании Feature-Sliced Design, в каких ситуациях он скорее вредит, чем помогает, и какие типичные ошибки совершают команды при его внедрении. Я буду опираться на практические сценарии, разбирать куски кода и структуры папок, чтобы вам было проще сопоставить это со своими проектами.

Переусложнение для маленьких и средних проектов

Когда FSD стреляют из пушки по воробьям

Для небольших приложений FSD часто приводит к архитектурному «перегреву». Структура становится слишком сложной по сравнению с реальной сложностью домена.

Посмотрите на условный ToDo-приложение, организованное по FSD:

src/
  app/
    providers/
    routing/
  processes/
    auth/
  pages/
    todos/
      ui/
      model/
      lib/
  features/
    add-todo/
      ui/
      model/
    toggle-todo/
      ui/
      model/
  entities/
    todo/
      ui/
      model/
  shared/
    ui/
    lib/
    api/

Для простого ToDo здесь уже слишком много уровней. Чтобы отследить путь данных, вам нужно прыгать между entities/todo, features/add-todo, features/toggle-todo, pages/todos. Это замедляет работу, хотя логики немного.

Как это выглядит в коде

Допустим, у вас есть небольшая форма добавления задачи. В классическом «упрощенном» подходе она могла бы выглядеть так:

// components/AddTodoForm.tsx
import { useState } from "react";

export function AddTodoForm({ onAdd }) {
  const [title, setTitle] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault();

    // Вызываем пропс-коллбек
    onAdd(title);

    setTitle("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(event) => setTitle(event.target.value)}
        placeholder="Новая задача"
      />
      <button type="submit">Добавить</button>
    </form>
  );
}

Теперь давайте посмотрим, как это может «разъехаться» в FSD при чрезмерном следовании слоям:

// features/add-todo/model/useAddTodo.ts
import { useState } from "react";
import { useTodosStore } from "entities/todo/model/store";

export function useAddTodo() {
  const [title, setTitle] = useState("");
  const { addTodo } = useTodosStore();

  const changeTitle = (value: string) => {
    // Обновляем локальный стейт
    setTitle(value);
  };

  const submit = () => {
    // Отправляем задачу в стор сущностей
    addTodo(title);
    setTitle("");
  };

  return {
    title,
    changeTitle,
    submit,
  };
}
// features/add-todo/ui/AddTodoForm.tsx
import { useAddTodo } from "../model/useAddTodo";

export function AddTodoForm() {
  const { title, changeTitle, submit } = useAddTodo();

  const handleSubmit = (event) => {
    event.preventDefault();
    submit();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(event) => changeTitle(event.target.value)}
        placeholder="Новая задача"
      />
      <button type="submit">Добавить</button>
    </form>
  );
}

Те же 2–3 строки логики теперь разнесены по слоям. Для сложной фичи это плюс, но для простых случаев — лишний шум.

Итоговый недостаток

  • Высокий «входной порог» даже для простых задач.
  • Увеличенное количество файлов и абстракций.
  • Риск того, что команда будет тратить больше времени на поддержку структуры, чем на реально важную бизнес-логику.

Сложность входа для новой команды

Архитектура, которую сначала нужно выучить

Чтобы эффективно работать с FSD, нужно понимать:

  • какие есть слои (app, processes, pages, features, entities, shared);
  • какие типы зависимостей допустимы между слоями;
  • как именно разделять «feature», «entity» и «page»;
  • как именовать фичи и сущности;
  • как работать с слайсами внутри слоя (model, ui, lib и т.д.).

Новичок, который не знаком с FSD, часто видит примерно следующее:

src/
  app/
  processes/
  pages/
  features/
  entities/
  shared/

и не понимает, куда класть новый код. Это приводит к «архитектурному ступору», когда разработчик тратит время не на реализацию, а на раздумья о размещении файла.

Типичный сценарий: «Куда положить этот хук?»

Смотрите, я покажу вам типичный спор в команде:

  • Хук используется в нескольких фичах — тогда, кажется, он должен быть в shared/lib или shared/model.
  • Но этот хук завязан на конкретное доменное понятие — значит, вроде бы ему место в entities.
  • При этом сейчас он используется только в одной фиче — и его пытаются положить внутрь features/.

В итоге одно и то же решение может быть реализовано тремя разными способами в рамках одного проекта, если нет четкого гайдлайна. Это создает хаос и снижает предсказуемость структуры.

Почему это считается недостатком

  • Требуется дополнительная документация по архитектуре внутри команды.
  • Ошибки размещения кода становятся нормой, а не исключением.
  • Код-ревью усложняется: ревьюеры тратят время на обсуждение размещения файлов вместо логики.

Риск переинженеринга и абстракций ради абстракций

Когда FSD подталкивает к лишним уровням

FSD часто воспринимают как сигнал «всегда выносить все в отдельную фичу/сущность». В результате даже простая логика «подписаться на рассылку» превращается в полноценную feature со своим model, ui, lib.

Посмотрите на пример чрезмерного усложнения.

features/
  newsletter-subscription/
    ui/
      SubscriptionForm.tsx
    model/
      useSubscription.ts
      types.ts
    lib/
      validators.ts

Но при этом реальная логика очень простая:

  • Один POST-запрос на API.
  • Одно поле email.
  • Одна кнопка.

Вместо этого можно было бы спокойно обойтись отдельным компонентом и хуком в shared, если бизнес-ценность этой «фичи» невелика.

Как выглядит переинженеринг в коде

Посмотрите, как можно усложнить простой кейс:

// features/newsletter-subscription/model/useSubscription.ts
import { useState } from "react";
import { subscribeToNewsletter } from "shared/api/newsletter";
import { validateEmail } from "../lib/validators";

export function useSubscription() {
  const [email, setEmail] = useState("");
  const [error, setError] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  const changeEmail = (value: string) => {
    // Проверяем email на фронте
    if (!validateEmail(value)) {
      setError("Некорректный email");
    } else {
      setError(null);
    }
    setEmail(value);
  };

  const submit = async () => {
    if (error || !email) return;

    setIsLoading(true);
    try {
      await subscribeToNewsletter(email);
    } finally {
      setIsLoading(false);
    }
  };

  return {
    email,
    error,
    isLoading,
    changeEmail,
    submit,
  };
}

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

Итоговый недостаток

  • FSD провоцирует выносить даже мелкие детали в отдельные фичи.
  • Увеличивается объем шаблонного кода (boilerplate).
  • Сложнее быстро вносить правки в простые сценарии.

Проблемы с навигацией по коду и поиском логики

Логика разрезана по нескольким слоям

Когда вы ищете причину бага, вам нужно проследить путь:

  1. pages — общая страница.
  2. features — конкретные пользовательские сценарии.
  3. entities — доменные сущности.
  4. shared — базовая утилита или API, которые в итоге вызываются.

Для небольших фич это превращается в лишнюю нагрузку на внимание. Вместо «найти компонент и посмотреть» вы часто вынуждены переключаться между 4–5 файлами.

Пример поиска проблемы

Допустим, у вас не обновляется список задач после добавления новой. В монолитном компоненте вы бы посмотрели:

  • как обновляется стейт;
  • как он рендерится.

В FSD путь может быть таким:

pages/todos/ui/TodosPage.tsx          # Страница
features/add-todo/ui/AddTodoForm.tsx  # Форма добавления
features/add-todo/model/useAddTodo.ts # Хук добавления
entities/todo/model/store.ts          # Стор задач
entities/todo/ui/TodoList.tsx         # Список задач
shared/api/todos.ts                   # API-клиент

Каждый шаг отдельно логичен, но суммарно это удлиняет цикл поиска.

Навигация в IDE становится менее прозрачной

Автодополнение и переход к определению (go to definition) в IDE работают хорошо внутри файла или модуля, но:

  • когда логика рассредоточена по множеству слоев и подкаталогов;
  • когда названия файлов повторяются (в каждом слое «index.ts», «model.ts», «types.ts»);

то разработчику приходится чаще смотреть на путь файла, а не только на имя.

Итоговый недостаток

  • Диагностика багов усложняется.
  • Время «первого контакта» с фичей увеличивается.
  • Контекст логики размыт по нескольким файлам.

Высокая зависимость от дисциплины команды

FSD без правил быстро превращается в хаос

Особенность FSD в том, что он задает общие уровни, но не заставляет строго следовать им. Если команда не придерживается дисциплины, возникают такие проблемы:

  • В shared попадает доменный код.
  • В entities реализуется логика, которая относится к конкретной фиче.
  • В features появляются огромные «комбайны», нарушающие принцип single responsibility.

Посмотрите на неправильно организованную структуру:

shared/
  api/
    user.ts      # Вроде бы общая сущность, но внутри куча бизнес-логики
  lib/
    cart.ts      # Логика корзины, хотя это уже домен

features/
  checkout/
    model/
      cartStore.ts  # Дублирует часть логики из shared/lib/cart.ts

В результате:

  • корзина размазана по shared и features;
  • логику сложно переиспользовать правильно;
  • команда тратит время на согласование, куда переносить код.

Как можно нарушить зависимости слоя

FSD предполагает, что:

  • features могут зависеть от entities и shared;
  • pages зависят от features, entities, shared;
  • entities не должны зависеть от features и pages и т.д.

Но в реальных проектах при спешке легко сделать вот так:

// entities/user/model/useUser.ts
import { trackLogin } from "features/analytics-login"; 
// Здесь мы тянем фичу в сущность, что нарушает зависимость слоев

export function useUser() {
  const login = () => {
    // Выполняем авторизацию
    // ...

    // Тречим login-событие
    trackLogin();
  };

  return { login };
}

Этот пример показывает типичный антипаттерн: сущность знает о конкретной фиче (аналитике логина), а должна быть от нее независимой.

Итоговый недостаток

  • FSD не гарантирует правильную архитектуру сам по себе.
  • Без правил и код-ревью структура быстро деградирует.
  • Требуется постоянный мониторинг архитектурной целостности.

Неполное соответствие сложным доменным моделям

Когда домен не укладывается в «features / entities»

Реальные бизнес-домены часто сложнее, чем «листинг задач» или «страница профиля». Например:

  • сложные workflow (многоэтапные процессы);
  • завязка на контекст пользователя;
  • кросс-доменные операции.

FSD предлагает делить на features и entities, но не всегда очевидно, как корректно смоделировать домен.

Например, домен «Заказ» в e-commerce:

  • корзина;
  • оплаты;
  • доставка;
  • скидки;
  • личный кабинет.

Если сделать «entity/order», она может разрастись до огромного «бога-объекта». Если же сильно дробить, возникает путаница, где заканчивается одна сущность и начинается другая.

Перекос в пользу UI-слайсов

FSD исторически фокусируется на фронтенд-слоях, в основном вокруг UI. В результате:

  • бизнес-правила иногда оказываются «размазаны» между несколькими features;
  • инварианты домена (что можно и нельзя по правилам) не всегда удобно выразить в рамках типичной FSD-структуры.

Посмотрите на пример:

entities/
  order/
    model/
      useOrder.ts
      validators.ts
    ui/
      OrderSummary.tsx

features/
  apply-coupon/
    model/
      useApplyCoupon.ts
  change-delivery/
    model/
      useChangeDelivery.ts

Где должны жить правила:

  • можно ли применить купон к этому типу заказа;
  • какие статусы доставки допустимы;
  • какие ограничения по комбинациям купонов и методов доставки?

Если часть логики спрятана в features, а часть — в entities/order/model, со временем становится сложно понимать, где истина.

Итоговый недостаток

  • Сложные бизнес-домены плохо ложатся в простую схему layers + slices.
  • Риск размазывания бизнес-логики по нескольким слоям.
  • Трудности с формализацией инвариантов и правил домена.

Усложнение тестирования и мокирования

Разделение по слоям усложняет интеграционные тесты

Компоненты и хуки в FSD часто сильно декомпозированы. Это полезно для unit-тестов, но интеграционные сценарии становятся более многослойными.

Например, чтобы протестировать поведение страницы:

  • нужно замокать стор сущностей;
  • правильно замокать фичи (если они ходят в API);
  • учесть провайдеры из слоя app (роутинг, контексты).

Вот типичный тест для страницы в FSD-подходе (упрощенно):

// pages/todos/ui/TodosPage.test.tsx
import { render, screen } from "@testing-library/react";
import { TodosPage } from "./TodosPage";
import { TodosProvider } from "entities/todo/model/TodosProvider";
import { QueryClientProvider } from "@tanstack/react-query";
import { createTestQueryClient } from "shared/lib/testUtils";

test("отображает список задач", async () => {
  const queryClient = createTestQueryClient({
    // Здесь мы настраиваем мок-ответы API
  });

  render(
    <QueryClientProvider client={queryClient}>
      <TodosProvider>
        <TodosPage />
      </TodosProvider>
    </QueryClientProvider>
  );

  // Проверяем что элементы списка отобразились
  expect(await screen.findByText("Моя первая задача")).toBeInTheDocument();
});

Сама по себе структура теста не страшная, но количество провайдеров и зависимостей возрастает с ростом проекта. FSD способствует тому, что:

  • в слое app появляются дополнительные провайдеры;
  • в processes есть собственные обвязки;
  • в entities живут свои провайдеры/контексты.

Мокирование API и стора

Часто встречается ситуация, когда:

  • стор или API-клиент лежит глубоко в entities или shared/api;
  • фича работает поверх этого, но тестировать вы хотите именно фичу.

Тогда вам приходится:

  • либо мокировать исходный модуль (например, через jest.mock);
  • либо создавать тестовые реализации стора;
  • либо заводить отдельные тест-обертки.

Если структура модулей не продумана, мокирование становится хрупким и завязанным на структуру папок.

Итоговый недостаток

  • Интеграционные тесты становятся многослойными.
  • Требуется аккуратное проектирование модулей для удобного мокирования.
  • Любые изменения архитектуры могут ломать тесты, если они слишком завязаны на структуру.

Сложности миграции существующих проектов на FSD

Полный рефакторинг редко возможен

Многие пытаются внедрять Feature-Sliced Design в уже существующий проект, который:

  • организован по другой структуре (по типам файлов, по модулям, по роутам);
  • содержит «god-components» и большие модули;
  • слабо типизирован.

Внезапный «переезд» на FSD приводит к:

  • большим рефакторингам, в которых легко сделать регрессии;
  • временной деградации архитектуры (часть по старому, часть по новому);
  • сложности с навигацией, потому что структура смешанная.

Вот как может выглядеть переходный период:

src/
  components/             # Старый подход
    Header/
    Footer/
    TodoList/
  store/                  # Старый глобальный стор
  app/                    # Новый FSD-слой
  pages/                  # Новый FSD-слой
  features/               # Новые фичи
  entities/               # Новые сущности
  shared/                 # Переиспользуемые модули

Пока вы не завершите миграцию, разработчикам нужно держать в голове два подхода одновременно.

Постепенный переход требует четкого плана

Чтобы миграция прошла безболезненнее, нужен план:

  • какие части кода остаются «как есть»;
  • какие новые фичи сразу делать в FSD;
  • какие старые модули постепенно переносить и по каким критериям.

Без этого планирования FSD воспринимается как «еще один слой хаоса», а не как решение.

Итоговый недостаток

  • Внедрение FSD в существующий проект требует серьезных усилий.
  • Некоторое время структура будет смешанной, что усложняет навигацию.
  • Нужен миграционный план, иначе затраты превышают пользу.

Зависимость от инструментов и конвенций

Необходимость дополнительных инструментов

Чтобы работать с FSD комфортно, команды часто добавляют:

  • линтеры, проверяющие импорт между слоями;
  • генераторы модулей/фич (cli-скрипты);
  • шаблоны (blueprints) для новых сущностей и фич.

Смотрите, пример правила ESLint, запрещающего неправильные импорты:

// .eslintrc.js - условный пример
module.exports = {
  rules: {
    "fsd/layers-imports": [
      "error",
      {
        alias: "@", // псевдоним для src
        ignoreImportPatterns: ["**/test-utils/**"],
      },
    ],
  },
};

Без таких инструментов:

  • легко нарушить зависимости между слоями;
  • трудно поддерживать единообразную структуру импорта;
  • ревьюеры вынуждены вручную отслеживать архитектурные ошибки.

Жесткая привязка к структуре

Если в проекте сильно «зашита» FSD-структура:

  • пути импорта используются повсюду;
  • есть множество относительных импортов между слайсами;
  • код-сплиттинг и бандлинг настроены под слои FSD;

то любое желание «перекроить» архитектуру потребует значительных изменений.

Итоговый недостаток

  • FSD комфортен, когда есть поддерживающие инструменты, а это дополнительные затраты.
  • Проект оказывается привязанным к определенной структуре.
  • Любой переход на другой подход или пересмотр слоев становится дорогим.

Отсутствие «официального» стандарта и разночтения

Разные интерпретации FSD в разных командах

Хотя у FSD есть документация и подход, нет жесткого стандарта «один в один». В результате:

  • команды по-разному трактуют границы слоев;
  • одни заводят widgets, другие — нет;
  • кто-то объединяет processes и pages по смыслу, кто-то разделяет строго.

Посмотрите на две возможные структуры:

Вариант 1:

src/
  app/
  processes/
  pages/
  features/
  entities/
  shared/

Вариант 2:

src/
  app/
  pages/
  widgets/
  features/
  entities/
  shared/

Или даже упрощенный:

src/
  app/
  pages/
  features/
  entities/
  shared/

Разные команды под «Feature-Sliced Design» понимают разные конкретные реализации. Это создает дополнительные трудности, если вы:

  • переходите между компаниями;
  • подключаете новых разработчиков, знакомых с «другим» вариантом FSD;
  • читаете статьи, где используется немного иная трактовка.

Итоговый недостаток

  • Нет единого жесткого стандарта FSD.
  • Опыт работы с FSD не всегда переносится напрямую в другой проект.
  • Появляется «архитектурный диалект» внутри каждой команды.

Заключение

Feature-Sliced Design дает мощный набор инструментов для организации фронтенд-архитектуры, но за это приходится платить. Основные недостатки, с которыми вы столкнетесь на практике:

  • переусложнение структуры для небольших и средних проектов;
  • повышенный порог входа и необходимость обучать команду не только фреймворку, но и архитектурным конвенциям;
  • риск переинженеринга, когда даже простая логика обрастает слоями и файлами;
  • усложнение навигации по коду, поиска логики и диагностики багов;
  • сильная зависимость от дисциплины и инструментов, без которых FSD легко превращается в хаос;
  • ограниченная удобность при работе с действительно сложными доменными моделями;
  • непростая миграция из существующей структуры кода на FSD;
  • отсутствие единого общепринятого стандарта, что приводит к разночтениям.

Если вы рассматриваете FSD для своего проекта, важно трезво оценить:

  • масштаб и сложность домена;
  • размер и опыт команды;
  • готовность инвестировать в обучение, документацию и инструменты;
  • необходимость долгосрочной масштабируемости именно на уровне фронтенда.

FSD хорошо работает там, где архитектурные затраты окупаются масштабом продукта и команды. Но если пытаться использовать его «по чек-листу» везде, можно легко столкнуться с перечисленными drawbacks и потратить значительные усилия на поддержку структуры, которая не приносит достаточной пользы.

Частозадаваемые технические вопросы по теме и ответы

Как ограничить неправильные импорты между слоями в FSD

Используйте ESLint-правила. Практический шаг:

  1. Установите eslint-plugin-boundaries или специализированный плагин для FSD.
  2. Опишите слои как модули.
  3. Настройте правило, запрещающее, например, импорт features из entities.

Мини-инструкция:

  • создайте конфиг модульных границ;
  • добавьте правило в .eslintrc;
  • включите проверку в CI.

Как организовать алиасы для слоев чтобы не писать длинные относительные пути

Шаги:

  1. В tsconfig.json добавьте paths, например: "@features/*": ["src/features/*"].
  2. В bundler-конфиге (Webpack, Vite) настройте те же алиасы.
  3. Проверьте, что IDE понимает алиасы (обычно это автоматом).

Теперь вы можете импортировать так:

import { AddTodoForm } from "@features/add-todo";

Как постепенно вытаскивать логику из «god-component» в FSD

Рекомендуемая последовательность:

  1. Выделите доменную сущность и создайте entities/<entity>/model и entities/<entity>/ui.
  2. Вынесите работу со стором и типы в model.
  3. Перенесите UI-часть в компоненты ui.
  4. Затем постепенно выносите отдельные пользовательские сценарии в features.

На каждом шаге проверяйте, что импорты соответствуют слоям.

Как настроить code splitting с учетом FSD слоев

Подход:

  1. Разделите бандлы по роутам (обычно слой pages).
  2. Внутри страниц используйте динамический импорт для тяжелых features и widgets.
  3. Старайтесь, чтобы shared и entities попадали в общие чанки, а не дублировались.

Пошагово:

  • в роутере используйте lazy/Suspense;
  • вынесите большие фичи в отдельные динамические импорты;
  • анализируйте бандл-репорты, чтобы убедиться, что разбиение эффективное.

Как документировать правила размещения кода в FSD проекте

Мини-инструкция:

  1. Создайте файл ARCHITECTURE.md в корне репозитория.
  2. Описывайте:
    • зачем выбран FSD;
    • какие слои используются именно у вас;
    • примеры размещения кода для типичных случаев.
  3. Добавьте в документ реальные примеры структуры папок и кода.
  4. Сделайте ссылку на документ обязательной частью онбординга для новых разработчиков.
Преимущества Feature-Sliced Design - как этот подход меняет разработку фронтендаСтрелочка вправо

Все гайды по Feature-sliced_design

Открыть базу знаний

Отправить комментарий