Two directories had grown past the plan's ≤4 files per folder constraint: shared/ui (5 primitives, and the kit keeps growing) and pages/public (5 pages, counting auth/ as a subfolder). Split shared/ui by concept — text-field/ and share-code/ each with their own tests/ — and move PageTitle to shared/layout, where the rest of the page chrome lives. Flat UI tests become per-component tests; the PageTitle test follows the component. Move LoginPage and RegisterPage into the existing pages/public/auth/ beside the AuthSplit layout they both use. No behaviour change: 83 tests, clean tsc build, 1 known oxlint warning.
7.1 KiB
Frontend Architecture (продумано заранее, реализация — следующий план)
Статус: структура согласована до написания implementation-плана фронтенда, чтобы план сразу проектировался под правильные границы, а не переписывался после первого клубка спагетти. Стек: React + Vite + TypeScript + Tailwind (см.
TODO.md, Фаза 10). Правила платформы (≤250 строк/файл и т.д.) — там же.
Принцип: структура по фичам, не по типу файла
Раскладывать src/ на /components, /hooks, /services, /types вперемешку —
самая частая ошибка в React-проектах: одна фича размазывается по всему дереву
папок, и её нельзя удалить или вынести, не задев остальное. Вместо этого —
каждая фича владеет всем, что ей нужно, в одной папке.
frontend/src/
features/
auth/ # регистрация/логин, device-link экран
components/
hooks/
api.ts # вызовы к accounts-service через gateway
types.ts
configs/ # 4 слота, share-коды, витрина
components/
hooks/
api.ts
types.ts
avatars/
admin/ # /admin, видим только role=admin
shared/ # только то, что реально используют 2+ фичи
components/ # кнопки, инпуты, модалки — дизайн-система
hooks/ # useAuth (общий контекст сессии), useDebounce и т.п.
api/
client.ts # общий fetch-wrapper: base URL gateway, JWT в заголовке,
# обработка 401 (refresh) и 429 (rate-limit) в одном месте
types.ts # общие DTO, если совпадают на нескольких фичах
i18n/ # LanguageSwitch, common.ru.ts / common.en.ts (typed dictionaries)
pages/ # роуты верхнего уровня, тонкие — просто собирают
# фичи в layout, никакой бизнес-логики
app/ # App.tsx, routes.tsx, i18n.ts (initI18n), i18next.d.ts (typed keys)
main.tsx
Каждая фича, у которой есть строки для пользователя, также владеет своей
i18n/ подпапкой (ru.ts, en.ts) — одно пространство имён i18next на
фичу (auth, landing, ...), русские словари как источник (as const),
английские типизированы через satisfies Translation<typeof ru> из
shared/i18n/common.ru.ts, так что расхождение ключей — ошибка компиляции.
Правило раздела shared/feature: если код используется ровно в одной фиче —
он живёт в этой фиче, а не в shared/. Переносить в shared/ только когда
появился второй потребитель — не заранее "на всякий случай" (YAGNI).
Лимиты из правил платформы (TODO.md) действуют и тут: ≤250 строк на
файл, ≤4 файла на папку (подпапки не считаются) — если в components/
внутри фичи появляется 5-й файл, выносим смысловую подпапку, а не копим.
Антипаттерны, которых сознательно избегаем
- Barrel-файлы
index.tsна каждую папку — удобны на вид, но ломают tree-shaking и IDE go-to-definition на больших проектах; экспортируем напрямую из файла, где определено - Prop drilling через 4+ уровня — если проп передаётся через компоненты, которым он сам не нужен (просто транзитом) — это сигнал на React Context (для auth-сессии) или на подъём компонента ниже по дереву, а не терпеть drilling
- Бизнес-логика в
pages/— страницы только компонуют фичи и layout; если в файле страницы появляетсяfetch/сложная derived-логика — она переезжает в соответствующую фичу - Один гигантский
api.tsна весь проект — обращения к API живут в папке своей фичи (features/configs/api.tsи т.д.), общий только low-levelshared/api/client.ts(fetch-wrapper) - any вместо нормальных типов — DTO с бэкенда типизируются по контрактам
из
backend/PLAN.md(Task 6:RegisterResponse,LoginResponseи т.д.), неany/unknownбез сужения
Тестирование
Тесты живут в подпапке tests/ внутри фичи (или shared/*), которую они
проверяют — не рядом с файлом (Component.test.tsx) и не в отдельном дереве
на верхнем уровне. Это всё ещё удаляется вместе с фичей при её удалении/
переносе, но не съедает бюджет "≤4 файла на папку" — колокация X.test.tsx
рядом с X.tsx вдвое сокращала бы вместимость каждой папки под этим правилом.
frontend/src/
features/
auth/
components/
hooks/
api.ts
types.ts
tests/
api.test.ts
components.test.tsx
shared/
ui/
Button.tsx
text-field/
TextField.tsx
tests/
text-field.test.tsx
Источники: Robin Wieruch — React Folder Structure Best Practices 2026, Mastering Modern React + Vite Folder Structure, Medium.
Единственное допустимое ребро shared → feature
shared/layout/ (шапка и каркас приложения) импортирует useSession из
features/auth/session: шапке нужно знать, вошёл ли пользователь. Это
единственный случай, когда shared/ зависит от фичи; остальной shared/
от фич не зависит.