Веб-разработка

Как проектировать frontend‑архитектуру которая переживёт рост команды

Как проектировать frontend‑архитектуру которая переживёт рост команды

Когда проект стартует, большинство команд совершают одну и ту же ошибку: они строят «быстрый прототип», который со временем превращается в «наследие» (legacy). В начале пути, когда в команде два разработчика, отсутствие структуры кажется преимуществом — вы двигаетесь быстро, не тратите время на бойлерплейт и легко договариваетесь на словах. Но как только команда разрастается до 10, 20 или 50 человек, отсутствие четкой архитектуры становится главным тормозом разработки.

Проблема не в том, что код становится плохим. Проблема в том, что когнитивная нагрузка на одного разработчика растет экспоненциально. Если каждый может изменить что угодно в любом месте, проект превращается в «большой ком грязи» (Big Ball of Mud), где фикс одного бага в хедере ломает форму оплаты в чекауте.

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

1. Смерть монолитной структуры папок

Стандартный подход components/, containers/, hooks/, services/ работает до первой сотни файлов. Когда в папке components лежит 150 компонентов, поиск нужного превращается в квест, а зависимости между ними становятся невидимыми и запутанными.

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

Принцип доменного разделения:

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

  1. modules/auth — всё, что касается авторизации (логика, компоненты, API).
  2. modules/billing — платежи, тарифы, счета.
  3. modules/user-profile — настройки профиля, аватары.

Внутри каждого модуля структура может быть идентичной, но модули должны быть максимально изолированы друг от друга. Если модулю billing нужны данные из auth, он не должен лезть во внутренности auth. Он должен использовать публичный API модуля.

2. Концепция Public API (Index-файлы)

Это один из самых недооцененных инструментов борьбы с хаосом. Суть проста: каждый модуль имеет один входной файл index.ts, который определяет, что этот модуль «выставляет наружу».

Почему это важно?

    • Инкапсуляция: Вы можете переписать внутреннюю реализацию модуля (заменить библиотеку управления состоянием или переделать верстку), и это не затронет остальное приложение, если публичный интерфейс остался прежним.
    • Контроль зависимостей: Если вы видите в импортах глубокую ссылку вроде import { InternalHelper } from '@/modules/auth/internal/helpers/utils', значит, кто-то нарушил границы модуля. Это сигнал к рефакторингу.

Правило простое: импорты из других модулей только из index.ts. Всё остальное — приватное.

3. Управление состоянием: борьба с «Глобальным Хаосом»

Самая большая ошибка растущих команд — свалить всё состояние приложения в один гигантский Store (Redux, Zustand или Vuex). В итоге Store превращается в свалку, где никто не знает, кто и когда изменил конкретный флаг, и почему приложение внезапно перерендерилось десять раз.

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

  1. Server State (Серверное состояние): Данные с бэкенда. Забудьте про ручное управление загрузками (isLoading, isError) и кэшированием. Используйте инструменты вроде TanStack Query (React Query) или SWR. Это убирает до 40% шаблонного кода из вашего Store.
  2. Local State (Локальное состояние): Состояние конкретного компонента или формы. Если данные нужны только в одном месте — держите их там. Не тащите в глобальный стор значение открытого выпадающего списка.
  3. Global State (Глобальное состояние): Только то, что действительно нужно всему приложению (тема, данные авторизованного пользователя, глобальные настройки).

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

4. FSD (Feature-Sliced Design) как стандарт

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

    • App — инициализация приложения (провайдеры, глобальные стили).
    • Processes — сложные сценарии, объединяющие несколько страниц (например, процесс оформления заказа).
    • Pages — композиционные единицы (страницы).
    • Widgets — крупные самостоятельные блоки (например, Header, Sidebar).
    • Features — конкретные бизнес-ценности (например, AddToWishlist, SearchProduct).
    • Entities — бизнес-сущности (User, Product, Order).
    • Shared — переиспользуемый код (UI-кит, хелперы, API-клиент).

Главное правило FSD — однонаправленный поток зависимостей. Слой может зависеть только от слоев, которые находятся ниже его. Shared не может зависеть от Pages. Это гарантирует отсутствие циклических зависимостей и делает код предсказуемым.

5. UI-Kit и дизайн-система: разделение ответственности

Когда команда растет, возникает проблема «зоопарка кнопок»: пять разных оттенков серого и три разных радиусов скругления углов.

Чтобы этого избежать, создайте отдельную библиотеку компонентов (Shared UI), которая:

  1. Не содержит никакой бизнес-логики.
  2. Работает только с пропсами.
  3. Документирована в Storybook.

Это позволяет дизайнерам и разработчикам говорить на одном языке. Когда бизнес просит «изменить все основные кнопки на синие», вы делаете это в одном месте в UI-Kit, а не правите 50 компонентов по всему проекту.

6. Автоматизация контроля качества (Guardrails)

Человеческий фактор неизбежен. Даже самые лучшие договоренности забываются в спешке перед релизом. Поэтому архитектурные правила должны быть автоматизированы.

Что внедрить обязательно:

  1. ESLint + Stylelint: Базовый гигиенический минимум.
  2. Husky + lint-staged: Чтобы в репозиторий не попадал код, который не проходит линтинг.
  3. Dependency Cruiser: Это инструмент, который может автоматически проверять ваши архитектурные границы. Вы можете настроить правило: «Запретить импорты из Entities в Shared». Если кто-то нарушит это правило, CI-пайплайн упадет.
  4. TypeScript в режиме strict: true: Без строгой типизации любой рефакторинг в большой команде превращается в игру «угадай, где упадет ошибка».

7. Стратегия миграции и технический долг

Никакая архитектура не будет идеальной навсегда. Важно заложить механизм эволюции кода.

    • Версионирование внутренних API: Если вы меняете структуру данных в модуле, не ломайте всё сразу. Создайте новый интерфейс, постепенно переведите на него потребителей, а затем удалите старый.
    • Документация «Почему», а не «Как»: Код говорит нам, как реализована функция. Документация (в README модулей или в Confluence) должна объяснять, почему было принято именно такое архитектурное решение. Это сэкономит сотни часов новым сотрудникам при онбординге.

Итог

Архитектура, которая переживает рост команды, — это архитектура, которая минимизирует взаимозависимости. Чем меньше связей между частями системы, тем проще ее изменять, тестировать и масштабировать.

Переход на модульный подход, внедрение Public API, четкое разделение состояний и автоматизация контроля — это не «оверхед», а инвестиция. В начале пути это может показаться медленным, но спустя полгода вы обнаружите, что ваша команда продолжает выдавать фичи с той же скоростью, что и в первый день, в то время как конкуренты тонут в собственном legacy.