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

Почему структура папок начинает мешать
Структура папок кажется удобной, когда продукт маленький. Страницы живут в одном месте, компоненты в другом, хуки где‑то ещё, и репозиторий выглядит аккуратно. Потом продукт растёт, и один пользовательский поток начинает задевать половину кода.
Возьмём простой пример с оформлением заказа. Продукт хочет добавить поле «название компании» для бизнес‑покупателей. Звучит как мелочь, но работа расползается по файлу страницы, двум компонентам формы, помощнику валидации, хуку корзины и мапперу API. Для пользователя это одно изменение. В коде это превращается в пять отдельных задач.
Это несоответствие замедляет команды. Разработчики перестают думать о пользовательском потоке и начинают думать о правилах папок. Где должна жить логика? Какой общий компонент стоит переиспользовать? Относится ли это к странице, хуку или к общему utils‑файлу? Эти вопросы добавляют трение, и со временем оно только усиливается.
Новые коллеги обычно чувствуют это первыми. Вместо того чтобы изучать, как работает продукт, они учат систему хранения в репозитории. Запоминают, куда кладут хуки форм, где лежат API‑помощники и в какой общей папке спрятана старая логика, до которой никто не хочет дотрагиваться. Это плохая сделка. Люди должны понимать поток регистрации или биллинга прежде, чем им понадобится карта правил нейминга.
Общий код часто становится настоящим беспорядком. Кнопка или хелпер для даты помещаются в общую папку. Бизнес‑логика — обычно нет. Как только команды начинают перетаскивать логику в shared, потому что её использует две фичи, в этой папке появляется куча случайных частей со всех сторон. Правило налогообложения рядом с форматтером поиска, хелпер корзины рядом с кодом профиля. Ничего не чувствуется однозначно принадлежащим, поэтому каждое изменение кажется рискованным.
Фичевые папки начинают иметь смысл, когда команда доходит до такой точки. Они связывают код с задачей пользователя. Когда приходит изменение продукта, команда остаётся близко к одной фиче вместо того, чтобы охотиться по всему репозиторию. Это не устранит все проблемные места, но срежет много лишних движений.
Начните с пользовательской задачи
Большинство изменений продукта начинается с пользовательской задачи. Команда добавляет гостевой чек-аут, меняет биллинг или сокращает регистрацию. Если кодовая база разделена на components, hooks, api и tests, одно небольшое изменение продукта может расползтись по всему репозиторию.
Лучше начать с задачи, которую пользователь пытается выполнить. Назовите папки по этой задаче: sign-up, checkout, billing, invite-team, reset-password. Эти названия совпадают с языком, который уже используют продуктовые менеджеры, дизайнеры и саппорт. Само по себе это убирает много перевода.
Именно поэтому фичевые папки обычно стареют лучше, чем папки по типу файла. Когда команда говорит о checkout, каждый должен уметь открыть одно место и увидеть экран, правила формы, вызовы API и тесты, связанные с checkout.
Держите части этой задачи рядом. В большинстве случаев одна папка фичи должна содержать UI для задачи, состояние и логику формы, соответствующие запросы API и тесты.
Это не значит, что каждый файл должен оставаться внутри папки навсегда. Общий код по‑прежнему нужен. Но код, специфичный для задачи, должен оставаться в фиче, пока действительно не понадобится нескольким фичам.
Простая структура может выглядеть так:
features/
checkout/
components/
hooks/
api/
state/
tests/
Этот макет помогает при изменениях продукта. Если бизнес на следующей неделе добавит «купить сейчас», команда сможет обновить checkout без рытья по глобальным папкам и угадывания, что ещё может сломаться.
Называние важнее, чем многие думают. Используйте продуктовые слова, а не технические метки. billing лучше, чем payment-module. sign-up лучше, чем user-onboarding-ui, если только эти две задачи действительно разнятся в продукте.
Для стартапов и плотных продуктовых команд выгода проста: меньше файлов для поиска, меньше случайных правок и меньше переработок при смене направления продукта. Когда имя папки совпадает с целью пользователя, код обычно проще править.
Выберите границы фичи
Большинство структур папок спотыкаются в одном и том же месте. Команды сгруппируют код по экрану, типу компонента или правилам фреймворка, прежде чем решить, какую задачу продукта каждый кусок на самом деле решает. Сначала выглядит аккуратно. Потом одно изменение продукта задействует пять папок.
Лучше начать с простой страницы и основных пользовательских потоков, нарисованных на ней. Держите это просто. Запишите цель пользователя, первое действие, шаги посередине и ожидаемый результат в конце.
Поток начинается, когда пользователь начинает задачу. Он заканчивается, когда достигается очевидный результат. «Открыть страницу биллинга» — не полезная граница. «Обновить способ оплаты и увидеть, что он сохранён» — полезная.
Этот небольшой сдвиг делает фичевые папки легче назвать и легче защищать, когда продукт меняется.
Владейте результатом, а не каждой частью
Когда два потока делят шаг, положите этот шаг в фичу, которая владеет результатом. Команды слишком рано выносят общий код. В результате получается общая папка, полная полусвязанных частей, до которых никто не хочет дотрагиваться.
Допустим, пользователи могут загрузить документ в онбординге и внутри настроек аккаунта. Виджет загрузки может выглядеть одинаково, но задачи разные. Онбординг владеет «завершить настройку». Настройки — «заменить существующий документ». Если правила, копирайт и состояния успеха различаются, оставьте каждую реализацию в своей фиче.
Перемещайте что‑то в shared только когда это действительно универсально. Хорошие примеры: кнопка, форматтер даты, хелпер размера файла или простой API‑клиент. Эти части не относятся к одной пользовательской задаче.
Быстрый тест помогает:
- Если вы изменяете этот код, вы ожидаете изменить один пользовательский поток или несколько?
- Несёт ли код бизнес‑правила или это просто базовый строительный блок?
- Сможет ли новый член команды понять, где это править, не перерывая проект?
Если ответ указывает на один результат — держите внутри фичи. Если код поддерживает множество потоков без продуктовых правил, перемещайте в shared. Линия не будет идеальной в первый день, но так будет гораздо чище, чем глобальная папка, полная «повторно используемого» кода, который на самом деле не повторно используем.
Собирайте одну фичу шаг за шагом
Если приложение уже запутано, не начинайте с полного переписывания. Выберите один поток, который каждую неделю раздражает команду. «Пригласить участника» или «сбросить пароль» обычно подходят, потому что у каждой есть UI, правила формы, вызовы API и вспомогательные файлы, разбросанные по приложению.
Создайте одну папку для этого потока и перемещайте только принадлежащие ей файлы. Держите перемещения небольшими. После каждого перемещения исправляйте импорты, запускайте тесты и открывайте экран в браузере. Это займёт больше времени, чем массовый перенос, но спасёт от мучительного вечера.
Простая настройка фичи часто начинается с компонентов для потока, хуков или локального состояния, используемых только там, API‑файлов для соответствующей backend‑логики и валидации, типов и мелких хелперов, которые никуда больше не годятся.
Затем добавьте один публичный файл входа, например index.ts. Этот файл должен экспортировать только те части, которые могут использовать другие области приложения. Возможно, это компонент страницы, один хук и тип. Всё остальное остаётся приватным внутри папки. Эта граница важна. Она не даёт другим фичам залезать внутрь и зависеть от случайных внутренних файлов.
Переименовывайте по ходу. Старые имена вроде sharedFormUtils или commonModal часто скрывают код, который на самом деле не общий. Если хелпер нужен только для invite‑потока, дайте ему явное имя и держите внутри. Чёткие имена обычно делают больше для организации фронтенд‑кода, чем ещё один уровень папок.
Останавливайтесь раньше, чем думаете. Вам не нужна идеальная структура в первый день. Остановитесь, когда новый разработчик сможет открыть одну папку и понять, как работает поток, не обшаривая весь код.
Это тест: одно место, одна задача и никаких неожиданных зависимостей, которые просачиваются наружу. Когда это достигнуто, поток готов к изменениям продукта без очередной глобальной зачистки.
Решите, что остаётся общим
Папка shared должна оставаться маленькой. Как только она превращается в хранилище случайного кода, каждая правка замедляется, потому что никто не знает, что безопасно править.
Переносите в shared только тогда, когда несколько фич используют код одинаково. Если одна фича использует кнопку, инпут или модал с особыми правилами, держите эту версию внутри фичи. Форма оплаты в checkout может использовать инпут, который выглядит универсальным, но также проверяет лимиты доставки или налоговые поля. Это код checkout, а не общий UI.
То же самое касается хелперов. Общие хелперы должны делать простую работу: форматировать дату или нормализовать номер телефона. Они не должны решать, кто получает скидку, когда пользователь может отменить подписку или какое предупреждение показывать. Эти правила принадлежат рядом с фичей, которая ими владеет.
API‑код часто находится посередине. Если несколько фич вызывают один и тот же бэкенд схожим образом, общий клиент имеет смысл. Auth, сессия пользователя или запросы профиля аккаунта обычно подходят туда, потому что много частей приложения их используют. Но если только фича returns вызывает endpoint возврата, держите клиента в папке returns, пока другая фича действительно не начнёт его использовать.
Быстрый тест для общего кода
Перед переносом файла задайте себе простые вопросы:
- Используют ли это как минимум две фичи без изменений, специфичных для фичи?
- Остаётся ли имя понятным вне оригинальной папки?
- Избегает ли файл продуктовых правил или поведения, специфичного для экрана?
- Если удалить одну фичу, будет ли этот файл иметь смысл?
Здесь фичевые папки либо остаются здоровыми, либо разваливаются. Команды часто создают shared слишком рано, потому что это кажется аккуратно. Обычно это делает ситуацию хуже.
Хорошая shared‑папка содержит скучный код. Это комплимент. Чем больше продуктового смысла в файле, тем ближе он должен быть к фиче, которая им владеет.
Простой пример с checkout
Checkout — хороший тест, потому что продуктовые команды постоянно его меняют. Правила доставки меняются, опции оплаты меняются, и всегда кто‑то хочет добавить ещё один шаг. Если ваши папки соответствуют задаче пользователя, изменения остаются локальными, а не расползаются по приложению.
Один чистый подход — позволить checkout владеть всем, что нужно покупателю, чтобы завершить покупку: просмотр корзины, адрес, оплата и сводка заказа. Это облегчает чтение потока для любого разработчика, который откроет код позже.
features/
checkout/
cart-review/
address/
payment/
order-summary/
promo-code/
totals/
api/
state/
ui/
Это работает, потому что файлы следуют потоку покупки, а не техническим категориям. Разработчик, который исправляет ошибку с налогами или добавляет Apple Pay, сможет оставаться внутри checkout/ большую часть времени.
Промокоды — частый источник грязного фронтенд‑кода. Команды часто переносят их в общую папку discounts слишком рано. Звучит аккуратно, но обычно это усложняет жизнь. Если промокод влияет только на поведение в покупку, держите его в checkout. Это его задача.
Сохранённые адреса другое дело. Сначала их можно держать в checkout: покупатель выбирает адрес, редактирует и идёт дальше. Как только та же адресная книга появляется в настройках аккаунта, управлении подписками или возвратах, тогда имеет смысл вынести её в account/ или в общий раздел. Переносите код, когда более чем одна реальная фича действительно нуждается в нём, а не потому, что повторное использование возможно в будущем.
Итоги заказа тоже должны быть рядом с правилами checkout. Отдельная папка math/ или pricing-utils/ может показаться аккуратной, но она скрывает бизнес‑логику. Итоги — это не просто числа. Они включают налоги, доставку, скидки, округления и иногда региональные правила. Когда эти файлы близки к order‑summary и payment, весь поток легче понять.
Именно поэтому фичевые папки обычно лучше выдерживают рефакторинг фронтенда. Код следует задаче покупки, поэтому команды могут править checkout без разрыва половины приложения.
Ошибки, которые создают лишнюю работу
Большая часть лишней работы не от самого изменения продукта. Она начинается, когда структура папок заставляет небольшое обновление казаться большим.
Обычная ошибка — разделить приложение на components, hooks и services на верхнем уровне. Сначала выглядит аккуратно, а потом старая проблема возвращается под новыми именами. Команда, которая меняет одну пользовательскую задачу, вроде паузы подписки, вынуждена искать по нескольким папкам, чтобы понять один поток.
Владение быстро размывается. UI в одном месте, логика получения данных — в другом, правила формы — где‑то ещё, и ни одна папка не говорит, кто должен вносить изменения.
Противоположная ошибка тоже проблематична. Некоторые команды делают фичевые папки настолько мелкими, что каждый модал, таблица и хук получают свою мини‑фичу. Это звучит модульно, но часто означает больше прыжков, больше импортов и больше времени на отслеживание зависимостей.
Если простое изменение отправляет разработчика через шесть мелких папок, структура противоречит продукту. Фичевая папка должна содержать значимый срез пользовательской работы, а не каждый маленький кусочек кода с именем.
Другой источник лишней работы — прятать бизнес‑правила в общих утилитах. Общий код должен быть скучным. Форматтеры дат, универсальные хелперы ввода и простые API‑обёртки — их место в shared. Правила типа «кто может вернуть деньги», «когда checkout может пропустить доставку» или «какой план показывает этот экран» должны оставаться рядом с фичей, которая этими правилами владеет.
Когда бизнес‑логика утекает в shared, она распространяется. Другие фичи начинают её вызывать, небольшие правки кажутся рискованными, и никто не знает, кто может безопасно её менять.
Переименование папок каждую спринт — ещё одна ловушка. Команды переименовывают billing в plans, потом в subscriptions, затем в revenue, надеясь, что следующее название решит путаницу. Чаще всего это не помогает. Если граница не та, более красивая метка её не спасёт.
Пара признаков проявляются рано:
- Одно изменение продукта затрагивает папки по всему приложению.
- Команды чаще спрашивают «кто владеет этим правилом?».
- В
sharedпоявляются продуктовые решения. - Небольшие рефакторы порождают большие PR с переименованиями.
Хорошие фичевые папки стареют хорошо. Вы должны часто менять код внутри них и редко менять их названия.
Быстрый чек‑лист для ревью
Структура папок проходит тест, когда разработчик может сделать обычное продуктовое изменение, не открывая половину проекта. Если небольшая задача превращается в охоту за сокровищами, структура уже замедляет команду.
Используйте этот чек при ревью или перед рефактором. Держите его простым. Если ответы неясны, скорее всего код тоже запутан.
- Может ли один человек проследить пользовательскую задачу от экрана до состояния, валидации и вызовов API внутри одной папки?
- Звучат ли имена папок как дорожная карта и продуктовый бриф?
- Остаётся ли общий код маленьким и скучным?
- Можно ли удалить одну фичевую папку без поломки несвязанных частей?
- Угадает ли новый сотрудник, куда принадлежит следующее изменение, за несколько минут?
Лучше проверять на реальных тикетах. Возьмите недавнюю задачу, например «добавить сообщение для подарка в checkout», и проиграйте её по дереву. В здоровой настройке путь очевиден. В запутанном коде люди скачут между общими хуками, глобальными файлами состояния и случайными компонентами.
Это ощущение важно. Когда путь очевиден, команды ставят задачи быстрее, ревью идут быстрее, и при рефакторинге фронтенда ломается меньше.
Что делать дальше
Выберите одну фичу, которая меняется почти в каждом спринте. Выберите то, что реально под давлением, например checkout, онбординг или настройки аккаунта. Это даст честный тест. Если структура помогает там, она обычно поможет и в более спокойных частях продукта.
Не начинайте с перемещения половины приложения. Возьмите одну фичу, соберите UI, состояние, действия, тесты и мелкие хелперы вместе и оставьте остальное в покое. Узкий тест лучше большой уборки, которая застопорится через неделю.
Напишите одно короткое правило для папки, прежде чем кто‑то начнёт добавлять новые файлы. Держите его простым: «Если код помогает только checkout работать, он остаётся в checkout. Если две фичи используют его и обе команды согласны с одинаковой формой, переместите в shared.» Правило вроде этого предотвратит обычные споры позже.
Небольшой чек‑лист:
- Начните с фичи, которая менялась в последних двух спринтах.
- Перемещайте только код, который фича использует ежедневно.
- Держите правило для общего кода в одно‑двух предложениях.
- Оставьте старые папки, пока новая структура не покажет свою полезность.
Затем используйте её в двух релизах и наблюдайте обычную работу. Нашли ли люди файлы быстрее? Оставались ли мелкие изменения внутри фичевой папки? Начала ли shared расти без причины? Эти ответы важнее, чем аккуратность структуры в первый день.
Через два релиза подправьте границы. Если одна папка всё время собирает несвязанный код — разделите её. Если две папки дублируют логику — вынесите её. Хорошая организация фронтенд‑кода обычно улучшается через несколько маленьких корректировок, а не одним идеальным планом.
Если команда застряла, полезно получить внешний взгляд перед тем, как переписывать всё. Oleg Sotnikov на oleg.is работает как fractional CTO и стартап‑советник, помогая компаниям наводить порядок в архитектуре, инфраструктуре и AI‑ориентированных рабочих процессах разработки без превращения работы в долгий рибилд.
Начните с одной папки на этой неделе и оценивайте по тому, как легко команда сделает следующее изменение.
Часто задаваемые вопросы
When should I switch to feature folders?
Переключайтесь, когда одно изменение продукта заставляет править файлы в components, hooks, api и tests по всему репозиторию. Если разработчики тратят больше времени на поиск, чем на правки одного пользовательского потока, текущая структура тормозит команду.
What counts as a feature?
Рассматривайте фичу как одну пользовательскую задачу с понятным результатом, например checkout, billing или reset-password. «Открыть страницу биллинга» слишком расплывчато; «обновить способ оплаты и увидеть, что он сохранён» — гораздо лучшее описание границы.
How should I name feature folders?
Используйте продуктовые слова, которые команда уже знает: checkout, invite-team, account-settings. Не применяйте технические ярлыки вроде payment-module, если только это имя не соответствует реальной области продукта.
What should stay inside a feature folder?
Держите вместе UI, локальное состояние, валидацию, вызовы API, тесты и мелкие хелперы для этой задачи. Если код помогает только одному потоку работать — оставьте его внутри фичи.
What belongs in a shared folder?
Переносите в shared только простые строительные блоки: кнопки, форматирование дат, хелперы для размеров файлов или базовый API‑клиент. Как только файл содержит правила скидок, проверки налогов или текст экрана, он должен находиться рядом с фичей, которой это правило принадлежит.
Should I reorganize the whole app at once?
Нет. Начните с одной болезненной фичи: переместите несколько файлов, исправьте импорты, прогоните тесты и задеплойте. Небольшой эксперимент покажет, помогает ли новая структура, прежде чем трогать остальное приложение.
Why add an index.ts file to a feature folder?
Один index.ts даёт остальному приложению одну понятную точку входа. Это предотвращает случайные импорты из внутренних файлов и упрощает дальнейшую очистку.
What if two features use similar code?
Держите отдельные версии до тех пор, пока поведение действительно не совпадает. Виджет загрузки в онбординге и в настройках аккаунта может выглядеть одинаково, но отличаться правилами, текстом и состояниями успеха — значит, им место в разных фичах.
How can I tell the current structure is causing churn?
Смотрите на тикеты. Если простое изменение отправляет людей по всему репозиторию, никто не знает, кто владеет правилом, или shared постоянно наполняется логикой продукта — структура работает против вас.
How do I know the new structure is working?
Проверяйте на реальном тикете, например добавить поле в checkout. Если один разработчик может проследить экран, состояние, валидацию и вызовы API в одном месте без поиска по репозиторию — новая структура работает.