Преимущества Feature-Sliced Design - как этот подход меняет разработку фронтенда

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

Олег Марков

Введение

Feature-Sliced Design (FSD) — это методология архитектуры фронтенд-приложений, которая помогает выстраивать масштабируемую, предсказуемую и удобную в сопровождении структуру проекта. В отличие от классического разделения на слои по типам (components, services, utils), здесь все крутится вокруг фич и сценариев пользователя.

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

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


Краткий обзор Feature-Sliced Design

Основные принципы

Давайте сначала обозначим ключевые принципы FSD, чтобы дальше было проще связывать их с преимуществами:

  1. Фиче-центричность
    Главная единица архитектуры — фича (feature), а не страница или компонент. Именно фичи несут в себе бизнес-ценность.

  2. Слои (layers)
    Приложение разделяется на уровни ответственности. Классическая схема выглядит примерно так:

    • app
    • processes
    • pages
    • features
    • entities
    • shared
  3. Срезы (slices)
    Внутри слоев код разбивается по доменным областям (например, profile, cart, product), чтобы логика бизнеса не размазывалась по всему проекту.

  4. Явные зависимости
    Каждый слой и каждый модуль имеет строго определенные правила, от кого он может зависеть. Более “верхние” слои могут зависеть от нижних, но не наоборот.

  5. Public API модулей
    Каждый доменный срез (feature, entity и т.д.) предоставляет наружу понятный «публичный API» — обычно через файл index.ts или public.ts. Внешний код не лезет внутрь структуры модуля.

Смотрите, я покажу вам, как это выглядит в типичном React-проекте.

src/
  app/
    providers/
    index.tsx        // Точка входа приложения
  processes/
    auth/
      model/
      ui/
      index.ts
  pages/
    profile/
      ui/
        ProfilePage.tsx
      index.ts
  features/
    edit-profile/
      model/
        useEditProfile.ts
      ui/
        EditProfileForm.tsx
      index.ts        // Публичный API фичи
  entities/
    user/
      model/
        userStore.ts
      ui/
        UserCard.tsx
      index.ts        // Публичный API сущности User
  shared/
    ui/
      Button/
        Button.tsx
        index.ts
    lib/
      api/
      form/
    config/

Комментарии к структуре:

// app — слой инициализации приложения
// processes — долгоживущие процессы, вроде авторизации
// pages — страницы, которые собирают фичи и сущности
// features — законченные пользовательские сценарии
// entities — бизнес-сущности (User, Product и т.п.)
// shared — переиспользуемые блоки без знания домена

Теперь, когда базовая картинка есть, давайте разберем преимущества.


Преимущество 1. Масштабируемость структуры проекта

Проблема “папки components” и хаоса при росте

В классической архитектуре фронтенда часто можно увидеть такое:

src/
  components/
    Header.tsx
    Footer.tsx
    ProfileForm.tsx
    ProductCard.tsx
    ...
  services/
  utils/
  pages/

Сначала это кажется удобным, но со временем:

// Компоненты разных доменов перемешиваются
// Тяжело понять, какие компоненты относятся к какой части бизнес-логики
// Изменения в одной фиче часто затрагивают файлы в разных местах

Feature-Sliced Design решает эту проблему за счет фокусировки на доменных областях и фичах.

Как FSD улучшает масштабируемость

За счет деления на слои и срезы каждый новый функционал ложится в понятное место. Давайте разберемся на примере добавления фичи «Редактирование профиля пользователя».

Вместо того чтобы искать “подходящее место” в папке components, вы заводите отдельный модуль фичи:

src/
  features/
    edit-profile/
      model/
        useEditProfile.ts  // Логика работы формы
      ui/
        EditProfileForm.tsx  // Компонент формы
      api/
        updateProfile.ts     // Запрос к API
      index.ts               // Публичный API фичи

Теперь вы увидите, как это выглядит в коде использования на странице:

// src/pages/profile/ui/ProfilePage.tsx

// Импортируем только из публичного API фичи
import { EditProfileForm } from "features/edit-profile"
import { UserCard } from "entities/user"

// Здесь мы собираем страницу из сущностей и фич
export const ProfilePage = () => {
  return (
    <div>
      {/* Отображаем данные пользователя */}
      <UserCard />
      {/* Даём возможность редактировать профиль */}
      <EditProfileForm />
    </div>
  )
}

Комментарии:

// ProfilePage ничего не знает о внутренней структуре edit-profile
// Вся логика фичи спрятана за её публичным API
// При расширении фичи (валидация, шаги, доп. поля) структура профиля не ломается

За счет такой организации:

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

Преимущество 2. Предсказуемые зависимости и контроль над связями

Зачем контролировать зависимости

Без архитектурных правил удобно быстро начинать, но потом возникает:

// Циклические зависимости между модулями
// Сильная связанность — изменения в одном месте ломают другие
// Сложность тестирования — модуль тащит за собой “полпроекта”

Feature-Sliced Design вводит строгие правила: кто от кого может зависеть. Это делает архитектуру более устойчивой.

Классическое правило слоев (упрощенно):

// shared — базовый слой, от него могут зависеть все
// entities — могут зависеть от shared
// features — могут зависеть от entities и shared
// pages — могут зависеть от features, entities, shared
// app — может зависеть от всех слоев, так как он «корневой»

Смотрите, я покажу вам пример неправильной зависимости.

// ПЛОХО - фича тянет в себя страницу

// src/features/edit-profile/model/useEditProfile.ts
import { ProfilePage } from "pages/profile"

// Здесь фича знает о конкретной странице,
// на которой она может быть использована.
// Это нарушает принцип переиспользования.

Правильный вариант — фича не должна знать, где её используют:

// ХОРОШО - страница использует фичу, а не наоборот

// src/pages/profile/ui/ProfilePage.tsx
import { EditProfileForm } from "features/edit-profile"

export const ProfilePage = () => {
  return <EditProfileForm />
}

Комментарии:

// Фича становится переиспользуемой — её можно использовать на другой странице
// Нет циклов вида pages → features → pages
// Тестировать фичу можно отдельно от страниц

Визуализация потока зависимостей

Если кратко, зависимости идут сверху вниз:

app

processes

pages

features

entities

shared

Такой единый “вектор” сильно снижает риск циклов и случайной связности.


Преимущество 3. Легкость локализации изменений

“Локальные” изменения против “расползания” по коду

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

// в компоненты
// в services
// в utils
// в некоторые hooks, разбросанные по проекту

В FSD вся логика конкретной фичи локализована в её модуле. Давайте посмотрим на примере фичи “добавление товара в корзину”.

src/
  features/
    add-to-cart/
      model/
        useAddToCart.ts
      ui/
        AddToCartButton.tsx
      api/
        addToCartApi.ts
      index.ts

Теперь давайте посмотрим, что происходит в следующем примере использования этой фичи:

// src/entities/product/ui/ProductCard.tsx
import { AddToCartButton } from "features/add-to-cart"

export const ProductCard = ({ product }) => {
  return (
    <div>
      {/* Здесь показываем карточку товара */}
      <h3>{product.title}</h3>
      <p>{product.price}</p>

      {/* Кнопка добавления в корзину - отдельная фича */}
      <AddToCartButton productId={product.id} />
    </div>
  )
}

Комментарии:

// Фича add-to-cart “прилипает” к любой сущности Product
// Внутреннюю реализацию (API, store, обработчики ошибок) можно менять локально
// ProductCard не ломается от внутренних изменений фичи

Если вам нужно изменить бизнес-правила добавления в корзину (например, не давать добавлять более 10 единиц), вы идете только в папку features/add-to-cart. Вам не нужно проверять десятки рассыпавшихся по проекту обработчиков.


Преимущество 4. Улучшение командной работы

Разделение зон ответственности в команде

В реальных проектах над кодом работают несколько человек, часто параллельно. Когда структура неявная, разработчики начинают “наступать друг другу на ноги” — трогать одни и те же файлы, пересекаться по ответственности, конфликтовать при слиянии веток.

Feature-Sliced Design помогает распределить зоны:

  • один разработчик отвечает за фичу “поиск товаров”;
  • второй — за фичу “оформление заказа”;
  • третий — за “профиль пользователя”.

Смотрите, я покажу вам упрощенную картину ответственных:

features/
  search-products/      # Ответственный: dev1
  checkout/             # Ответственный: dev2
  edit-profile/         # Ответственный: dev3
entities/
  user/                 # Ответственный: dev3
  product/              # Ответственный: dev1
  order/                # Ответственный: dev2

Комментарии:

// Каждый владеет своей частью домена
// Изменения реже приводят к конфликтам в Git
// Код-ревью легче выстроить по зонам ответственности

Параллельная разработка фич

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

Давайте разберемся на примере:

  • dev1 делает фичу “wishlist” (список желаний)
  • dev2 делает фичу “recommendations” (рекомендованные товары)

Обе фичи опираются на одну сущность Product из entities. При этом каждая фича живет в своей папке и не вмешивается в код другой:

features/
  wishlist/
    ...
  recommendations/
    ...
entities/
  product/
    ...

Это существенно снижает “трение” между разработчиками и ускоряет разработку.


Преимущество 5. Упрощение повторного использования и изоляции фич

Фича как самостоятельный блок

В FSD фича — это не только UI-компонент, но и:

// логика (hooks, store, handlers)
// API-запросы
// валидация
// работа с сущностями

За счет такой упаковки фичу легко:

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

Покажу вам, как это реализовано на практике на примере фичи “логин по email и паролю”.

features/
  auth-by-email/
    model/
      useAuthByEmail.ts
    ui/
      AuthByEmailForm.tsx
    api/
      loginByEmail.ts
    index.ts

Публичный API фичи:

// src/features/auth-by-email/index.ts

// Экспортируем только то, что должно быть видно снаружи
export { AuthByEmailForm } from "./ui/AuthByEmailForm"
export { useAuthByEmail } from "./model/useAuthByEmail"

А вот как фича используется внутри страницы:

// src/pages/login/ui/LoginPage.tsx

import { AuthByEmailForm } from "features/auth-by-email"

export const LoginPage = () => {
  return (
    <div>
      <h1>Login</h1>
      {/* Вставляем готовую фичу авторизации */}
      <AuthByEmailForm />
    </div>
  )
}

Комментарии:

// Логику авторизации легко вынести в другое приложение
// При необходимости можно заменить реализацию loginByEmail.ts, не трогая LoginPage
// Добавление альтернативного способа входа (например, OAuth) не ломает текущую фичу


Преимущество 6. Улучшенная тестируемость

Тестирование фич и сущностей вместо “страниц целиком”

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

Рассмотрим фичу “редактирование профиля”. Внутри неё есть хук useEditProfile, который управляет состоянием формы и отправкой данных.

Теперь давайте посмотрим, что происходит в следующем примере теста:

// src/features/edit-profile/model/useEditProfile.test.ts
import { renderHook, act } from "@testing-library/react"
import { useEditProfile } from "./useEditProfile"

describe("useEditProfile", () => {
  test("должен вызывать onSuccess после успешного сохранения", async () => {
    const onSuccess = jest.fn()

    // Рендерим хук
    const { result } = renderHook(() => useEditProfile({ onSuccess }))

    // Вызываем метод сохранения
    await act(async () => {
      await result.current.submit({
        name: "John",
        email: "john@example.com",
      })
    })

    // Проверяем, что коллбек был вызван
    expect(onSuccess).toHaveBeenCalled()
  })
})

Комментарии:

// Мы тестируем только бизнес-логику фичи, без UI и без страниц
// Фича изолирована от остального приложения — легче мокать зависимости
// Тесты работают быстро и точечно

Если бы фича была “размазана” по страницам и общим hooks, тесты пришлось бы писать сложнее, сильно завязываясь на контекст всего приложения.


Преимущество 7. Лучшая читабельность и onboarding новичков

Когда вы открываете чужой проект

Типичная ситуация: вы открываете незнакомый фронтенд-проект и пытаетесь понять, где искать:

// логику корзины
// обработку профиля
// авторизацию
// фильтры каталога

В FSD все лежит на своих местах по доменам. Если вам нужно разобраться с корзиной, вы идете:

features/
  add-to-cart/
  remove-from-cart/
  update-cart-item/
entities/
  cart/
  product/

Если вам нужен профиль пользователя:

features/
  edit-profile/
  change-password/
  upload-avatar/
entities/
  user/

Давайте посмотрим, как это выглядит для новичка в проекте:

src/
  app/          # точка входа и окружение
  pages/        # конечные страницы
  features/     # основные пользовательские сценарии
  entities/     # бизнес-сущности
  shared/       # общие компоненты, утилиты и конфиг

Комментарии:

// Новичку достаточно несколько минут, чтобы понять структуру
// Не нужно запоминать “особые” места для специфических компонентов
// Локализация логики по доменам снижает порог входа


Преимущество 8. Гибкость при изменении бизнес-требований

Бизнес меняется — архитектура должна выдерживать

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

В FSD фичи и сущности спроектированы так, чтобы их можно было:

// расширять дополнительными сценариями
// заменять частями (например, новый дизайн UI, но старая логика)
// перераспределять между страницами

Давайте посмотрим, что происходит в следующем примере изменения требования:

  • Было: фича edit-profile доступна только на странице ProfilePage.
  • Стало: нужно дать возможность редактировать профиль сразу после регистрации на странице WelcomePage.

Без FSD можно было бы иметь “жёстко вшитую” форму в ProfilePage. При переносе на WelcomePage понадобилось бы дублирование или вынос в какой‑то общий компонент.

С FSD фича уже вынесена в отдельный модуль:

// src/pages/welcome/ui/WelcomePage.tsx
import { EditProfileForm } from "features/edit-profile"

export const WelcomePage = () => {
  return (
    <div>
      <h1>Welcome</h1>
      {/* Повторно используем ту же фичу */}
      <EditProfileForm />
    </div>
  )
}

Комментарии:

// Перенос функционала — это просто новый импорт
// Фича не привязана к конкретной странице
// Менять бизнес-процессы можно, не переписывая внутреннюю реализацию


Преимущество 9. Подготовленность к частичному рефакторингу и миграциям

“Рефакторим по кускам, а не сразу весь проект”

Feature-Sliced Design хорошо поддерживает идею поэтапного рефакторинга. Вы можете:

// взять одну доменную область (например, auth)
// выделить её в entities и features
// постепенно приводить к FSD-структуре
// не трогать остальной проект сразу

Смотрите, я покажу вам пример переходного состояния:

src/
  legacy/
    components/
      OldAuthForm.tsx
    services/
      authService.ts
  features/
    auth-by-email/
      model/
      ui/
      api/
  entities/
    user/

Комментарии:

// Параллельно живут старые и новые модули
// Новая фича auth-by-email уже организована по FSD
// Старый код постепенно выносится из legacy в entities/features

Так можно мигрировать большие монолитные фронтенды, не останавливая разработку и не переписывая все “с нуля”.


Преимущество 10. Облегченная интеграция с инструментами и линтерами

Архитектурные правила можно формализовать

Еще одно важное преимущество — FSD хорошо поддается автоматизации. Вы можете формализовать правила:

// какие слои могут импортировать какие
// откуда нельзя импортировать напрямую
// как должны выглядеть пути импорта

Например, с помощью ESLint и плагинов (страницы не могут импортировать страницы, фичи не могут импортировать app и т.д.).

Пример простого правила импорта в конфигурации ESLint (псевдокод):

// .eslintrc.js
module.exports = {
  rules: {
    "no-restricted-imports": [
      "error",
      {
        patterns: [
          {
            group: ["pages/*"],
            message: "Нельзя импортировать другие страницы напрямую",
          },
          {
            group: ["app/*"],
            message: "Нельзя импортировать слой app из нижних слоев",
          },
        ],
      },
    ],
  },
}

Комментарии:

// Такие правила помогают держать архитектуру в порядке
// Новички не смогут “случайно” нарушить слои
// Крупные команды получают единый стандарт


Заключение

Feature-Sliced Design — это не просто “еще одна структура папок”, а целостный подход к проектированию фронтенд-архитектуры на основе бизнес-доменов и пользовательских сценариев.

Ключевые преимущества, которые вы получаете:

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

Подход Feature-Sliced Design особенно хорошо раскрывается в средних и больших проектах, где сложность бизнеса и команды уже не позволяет полагаться только на “компонентную” структуру и личную дисциплину разработчиков. Если вы чувствуете, что ваш фронтенд уже начинает “расползаться” по структуре и становится трудно поддерживаемым, FSD дает понятный, практичный и ориентированный на продукт путь упорядочивания кода.


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

Как начать внедрять Feature-Sliced Design в уже существующий проект без тотального рефакторинга

  1. Выделите один домен, где больше всего боли, например auth или cart.
  2. Создайте базовые слои app, pages, features, entities, shared.
  3. Внутри выбранного домена перенесите код в entities и features по FSD-принципам.
  4. Страницы пока могут оставаться старыми, но использовать уже новые фичи.
  5. Постепенно переносите остальные домены, сохраняя рабочее состояние приложения.

Как правильно организовать импорты при использовании алиасов (features, entities и т.д.)

  1. Настройте алиасы на уровне сборщика (Webpack, Vite, TSConfig) для путей вида features/, entities/, shared/* и т.д.
  2. Импортируйте только из публичного API модуля — из index.ts внутри среза.
  3. Избегайте относительных импортов, которые “пробивают” вверх или в соседние домены.
  4. Добавьте ESLint-правила, запрещающие импорты внутрь private-файлов других модулей.

Как разделять логику между entities и features если кажется что всё относится к сущности

  1. В entities размещайте “чистое” представление сущности — типы, базовую бизнес-логику, UI-компоненты без сценариев.
  2. В features выносите конкретные пользовательские сценарии с участием сущности — изменения, формы, сложные взаимодействия.
  3. Если модуль сложно описать одной фразой “пользователь делает X”, скорее всего это entities.
  4. Если легко — это feature, даже если она сильно завязана на одну сущность.

Как использовать глобальное состояние (например Redux или Zustand) в контексте FSD

  1. Размещайте сторы рядом с тем модулем, к которому они логически принадлежат — чаще всего это entities или features.
  2. Не делайте один большой “global store” без привязки к доменам.
  3. В слое app можно держать только инфраструктурные сторы (навигация, конфиг, фичефлаги).
  4. Экспортируйте хук или селекторы через публичный API модуля, не раскрывая внутреннюю структуру стора.

Как поступать с кросс-доменной логикой которая затрагивает несколько сущностей и фич

  1. Определите, что именно является общим процессом — например “оформление заказа” затрагивает user, cart, payment.
  2. Вынесите этот процесс в слой processes как отдельный модуль.
  3. Внутри него оркестрируйте взаимодействие между features и entities, не дублируя их логику.
  4. Страницы должны работать уже с process как с “супер-фичей”, не влезая в его внутренние связи.
Стрелочка влевоНедостатки Feature Sliced Design - подробный разбор проблем и подводных камнейСравнение vs-atomic и Atomic Design во Vue проектахСтрелочка вправо

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

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

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