В современных фронтенд-проектах архитектура довольно быстро перестаёт быть «внутренним делом команды» и начинает напрямую влиять на скорость разработки, качество изменений и стоимость поддержки. По мере роста приложения усложняется навигация по кодовой базе, увеличивается количество пересечений между модулями, и любые изменения начинают требовать больше времени и осторожности. Нам было важно выбрать подход, который задаёт понятные правила, но при этом остаётся практичным и не мешает развиваться продукту в реальных условиях.
В данной статье мы расскажем про то, как мы использовали Feature-Sliced Design для архитектуры фронтенд-приложения и адаптировали методологию под page-based подход: какие слои оставили, какие добавили, как организовали страницы как самостоятельные модули и за счёт чего снизили destructive decoupling, сохранив удобство масштабирования и поддержки.
В данной статье мы расскажем про то, как мы использовали Feature-Sliced Design для архитектуры фронтенд-приложения и адаптировали методологию под page-based подход: какие слои оставили, какие добавили, как организовали страницы как самостоятельные модули и за счёт чего снизили destructive decoupling, сохранив удобство масштабирования и поддержки.
Что такое Feature-Sliced Design
Feature-Sliced Design (FSD) — архитектурный подход к разработке фронтенд-приложений. Если упростить, это набор правил, который помогает сделать проект более понятным, читаемым, поддерживаемым и масштабируемым. При этом важно учитывать, что в реальных проектах FSD почти всегда интерпретируют и адаптируют под контекст: у команд разные домены, разная динамика изменений и разная потребность в переиспользовании.
На нашем проекте мы также упростили методологию там, где это было оправдано, и оставили только те части, которые реально помогали решать ежедневные задачи команды.
На нашем проекте мы также упростили методологию там, где это было оправдано, и оставили только те части, которые реально помогали решать ежедневные задачи команды.
Какие слои мы оставили и почему
Из слоёв, которые предлагает методология, мы оставили следующий набор
и дополнили его своим слоем для стилей:
и дополнили его своим слоем для стилей:

- app — глобальные элементы приложения: роутинг, провайдеры, сторы, инициализация и прочая инфраструктура, важная для всего приложения.
- pages — модули страниц и локальная логика, относящаяся к конкретным страницам.
- features — пользовательские сценарии уровня продукта (например, авторизация).
- shared — переиспользуемые модули, которые можно относительно безболезненно применять в разных местах приложения и потенциально переносить в другие проекты.
- styles — отдельный слой, который мы добавили: глобальные стили приложения, SCSS-константы и миксины.
Page-based подход внутри FSD
Наш вариант можно описать как page-based: мы делаем страницы основными архитектурными единицами. Каждая страница
— самостоятельный модуль, который содержит всё необходимое
для работы: UI, бизнес-логику, локальные типы, стили и вспомогательные утилиты. Благодаря этому навигация по проекту становится естественной: чтобы понять, как работает конкретная часть продукта, чаще всего достаточно перейти в папку соответствующей страницы и видеть всё в одном месте.
Ключевой принцип, который мы для себя зафиксировали, звучит просто: держим связанные элементы рядом до тех пор, пока они не потребуются
в другом месте. Когда появляется реальная потребность
в переиспользовании, мы переносим код на более глобальный уровень.
— самостоятельный модуль, который содержит всё необходимое
для работы: UI, бизнес-логику, локальные типы, стили и вспомогательные утилиты. Благодаря этому навигация по проекту становится естественной: чтобы понять, как работает конкретная часть продукта, чаще всего достаточно перейти в папку соответствующей страницы и видеть всё в одном месте.
Ключевой принцип, который мы для себя зафиксировали, звучит просто: держим связанные элементы рядом до тех пор, пока они не потребуются
в другом месте. Когда появляется реальная потребность
в переиспользовании, мы переносим код на более глобальный уровень.
Как устроен модуль страницы в pages
Страница на слое pages обычно включает всё, что нужно только этой странице и не предполагается к использованию где-то ещё. Внутри страницы мы допускаем локальную модульность: крупные части можно разбивать
на подмодули, заводить helpers, хуки, константы и т.п.
на подмодули, заводить helpers, хуки, константы и т.п.

Это не про усложнение, а про то, чтобы сохранять структуру читаемой по мере роста функциональности.
Отдельно мы договорились, что у модуля должна быть публичная точка входа — как правило, index.tsx (или index.ts), который выступает публичным API.
Отдельно мы договорились, что у модуля должна быть публичная точка входа — как правило, index.tsx (или index.ts), который выступает публичным API.

Это позволяет фиксировать границы: наружу экспортируется только то,
что действительно нужно потребителям, а внутренние детали (локальные типы, стили, хелперы, подкомпоненты) остаются внутри модуля.
В результате импорт становится предсказуемым, а рефакторинг внутренних частей страницы меньше влияет на остальной код.
что действительно нужно потребителям, а внутренние детали (локальные типы, стили, хелперы, подкомпоненты) остаются внутри модуля.
В результате импорт становится предсказуемым, а рефакторинг внутренних частей страницы меньше влияет на остальной код.
Правило миграции: когда выносить код выше
Чтобы не получить преждевременную «универсальность» и не размыть ответственность слоёв, мы придерживаемся практики «локально
до последнего». Если модуль используется в рамках одной страницы
— он остаётся внутри страницы. Если появляется второй потребитель
— тогда принимаем решение о переносе.
Практически мы используем такие критерии:
до последнего». Если модуль используется в рамках одной страницы
— он остаётся внутри страницы. Если появляется второй потребитель
— тогда принимаем решение о переносе.
Практически мы используем такие критерии:
- переносим в shared, если это универсальный компонент/утилита без жёсткой привязки к домену страницы и его можно использовать в других контекстах;
- переносим в features, если это полноценный пользовательский сценарий уровня продукта (например, авторизация), который логически живёт «над страницами»;
- оставляем/переносим в app, если речь про инфраструктуру и сквозную конфигурацию приложения.
Как это решает проблему destructive decoupling
Одна из проблем, которую мы хотели устранить, — destructive decoupling: ситуацию, когда один логический модуль оказывается «размазанным» по разным папкам проекта (часть — в компонентах, часть — в утилитах, часть — в типах и т.д.). Page-based подход снижает вероятность такого расползания: всё, что относится к странице, находится вместе, и команде проще держать контекст. Когда переиспользование становится реальным, перенос выполняется осознанно и в правильный слой.
В результате у нас получилась структура, основанная на страницах приложения: каждая страница содержит модули, необходимые для своей работы. По сути это даёт эффект, близкий к rich domain pattern на фронтенде — доменная логика и UI остаются собраны вокруг своего контекста, а общие части постепенно оформляются как библиотека.
В результате у нас получилась структура, основанная на страницах приложения: каждая страница содержит модули, необходимые для своей работы. По сути это даёт эффект, близкий к rich domain pattern на фронтенде — доменная логика и UI остаются собраны вокруг своего контекста, а общие части постепенно оформляются как библиотека.
Пример: декомпозиция типового компонента на странице Bids
Наглядный пример — типовой компонент “элемент списка”, который относится к странице Bids. В таком случае мы размещаем его в ui сегменте страницы. Внутри компонента допускаем локальные вложенные элементы
и вспомогательные части, которые имеют смысл только в рамках этой страницы (например, локальные подкомпоненты и хелперы
— в соответствующих локальных папках).
При этом элементы, которые можно переиспользовать в других местах приложения или даже в других проектах, мы выносим в shared.
и вспомогательные части, которые имеют смысл только в рамках этой страницы (например, локальные подкомпоненты и хелперы
— в соответствующих локальных папках).
При этом элементы, которые можно переиспользовать в других местах приложения или даже в других проектах, мы выносим в shared.

Например, общий Button или универсальный форматтер даты — это хорошие кандидаты на переиспользование и должны жить отдельно от конкретной страницы. Так сохраняется логика: доменно-специфичное остаётся рядом
со страницей, а универсальное постепенно становится общим фундаментом проекта.
со страницей, а универсальное постепенно становится общим фундаментом проекта.
Итоги
Мы использовали FSD как основу, но адаптировали методологию под практические потребности проекта, сделав акцент на page-based структуре. Это помогло собрать связанный код в одном месте, упростить навигацию, снизить destructive decoupling и закрепить ясные правила, когда и куда переносить код при появлении переиспользования.
В результате архитектура остаётся понятной по мере роста продукта,
а команда получает предсказуемую структуру и понятные границы модулей.
В результате архитектура остаётся понятной по мере роста продукта,
а команда получает предсказуемую структуру и понятные границы модулей.
