Vite + React без хаоса: минимальная структура проекта

Vite + React. Границы папок важнее модного паттерна - через месяц без структуры components/ превращается в свалку всего проекта.

Vite быстрый, архитектуру не продаёт

create-vite поднимает проект за минуту. Через две недели в `components` лежит всё: кнопка, страница оплаты, запросы к API и модалка удаления.

Мелочь? Нет.

Новый экран - и вы уже боитесь что-то трогать.

Минимальная структура, которую я использую

src/
  app/          # роутинг, провайдеры
  pages/        # экраны
  widgets/      # крупные блоки страницы
  features/     # действия пользователя
  shared/
    api/        # запросы
    ui/         # кнопки, инпуты
    lib/        # утилиты

Это не религия FSD. Это границы: shared не знает про бизнес, features держит сценарии, pages собирают экран из виджетов.

Алиасы - меньше ../../../

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'node:path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') }
  }
})

Импорт `@/shared/api/orders` читается лучше, чем цепочка из точек.

Сроки плывут отсюда.

TypeScript paths настраиваю синхронно с Vite.

API не живёт в JSX

Компонент вызывает `loadOrders()`, а не собирает fetch с headers внутри разметки. Ошибки, повторы, типы - в одном месте.

Да, так бывает.

Так проще тестировать и менять бэкенд.

СлойЧто хранить
shared/apifetch-обёртки, типы ответов
featuresхуки и действия «отправить форму»
pagesсборка экрана, минимум логики
widgetsheader, footer, сложные секции
Хорошая структура не усложняет проект. Она снижает шанс, что новая кнопка сломает соседний экран.

Когда не усложнять

Лендинг на три экрана не обязан быть микросервисом на фронте. Если файлов мало - достаточно pages + shared. Папки features и widgets добавляю, когда появляется второй похожий сценарий, а не «на вырост».

Если проект заказной - фиксирую структуру в README на полстраницы: куда класть новый API, где лежат UI-кит и env. Это дешевле, чем объяснять голосом каждому новому человеку. Вопросы по стеку до старта - в материале до предоплаты.

Env, конфиг и деплой

Ключи API - только в .env на сервере, не в репозитории. В Vite публичные переменные с префиксом VITE_, секреты - через backend. .env.example в git, реальные .env - в .gitignore. Банально, но на каждом втором аудите нахожу ключ в клиенте.

Скрипты в package.json: dev, build, preview, lint. Husky или CI на lint - по желанию, но build должен проходить чисто перед деплоем. README с командой деплоя экономит нервы через полгода.

Когда вырастать из минимальной структуры

Появился второй тип пользователя, общие фильтры, модалки на пол экрана - пора делить features/widgets. До этого не создавайте пустые папки «на будущее». Структура должна отражать реальные границы, не wishlist.

Тесты и типы - по необходимости

На MVP лендинга достаточно TypeScript strict и eslint. Unit-тесты на утилиты и API-обёртки - да; snapshot всего UI - редко окупается. e2e на «форма отправилась» - один сценарий перед релизом, если есть staging.

Компоненты UI документирую Storybook только если их больше десятка и команда >1. Иначе - overkill.

Роутинг и code splitting

React Router или аналог: страницы lazy через React.lazy и Suspense для админки и редких экранов. Главная и лендинг - в основном bundle, если LCP критичен. Не режьте всё подряд - смотрите bundle analyzer.

Общие зависимости: date-fns вместо moment, lodash-es точечный import. Каждый «просто подключим ещё одну lib» - минус на mobile.

Error boundary на уровне page - пользователь видит «что-то сломалось, обновите», а не белый экран. Логируете в Sentry или console на staging минимум.

Состояние и формы

React Hook Form или аналог для форм - меньше ре-рендеров. Глобальный state (Zustand/Context) только для того, что реально глобально: user, theme, cart. Локальный useState на странице - норм.

Не тащите Redux на лендинг. Не тащите GraphQL если один REST endpoint формы.

Стили: CSS modules, Tailwind или styled - один подход на проект. Смешение трёх систем - боль поддержки.

Деплой: static на CDN/VPS, env на сервере, CI build on push. Preview deploy на каждый PR - кайф для заказчика, видит прогресс.

Git и code review на маленьком проекте

Даже лендинг на React я веду в git с понятными коммитами: «feat: contact form», «fix: mobile menu». Ветка main - прод, develop или feature-ветки - staging. Заказчик получает ссылку на staging, не на «у меня локально работает».

Pull request на изменения больше пятидесяти строк - сам себе review: что сломается на мобиле, не утекли ли ключи. Husky optional, но npm run build перед merge - обязательно.

Конфиг ESLint и Prettier

Один стиль форматирования с первого дня. Споры «таб или пробелы» не в чате с заказчиком, а в .prettierrc. TypeScript strict: noImplicitAny ловит половину багов до деплоя.

// tsconfig.json - минимум для заказного фронта
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "paths": { "@/*": ["./src/*"] }
  }
}

Документация для «через полгода»

README на полэкрана: как поднять dev, как собрать, куда деплоить, где env. Список env-переменных в .env.example. Если проект передаётся другому разработчику - он не должен звонить вам ночью.

Я добавляю ADR только когда решение неочевидное: «почему Zustand, а не Redux» - один абзац в docs/adr-001-state.md. Не encyclopedia, а якоря памяти.

Структура папок - карта. README - легенда. Без легенды карта бесполезна через три месяца.

Если заказчик спрашивает «зачем React на лендинге» - честный ответ в материале React vs HTML. Вопросы по стеку до старта - до предоплаты. Оценка рефакторинга - на странице цен.

Миграция без остановки продакшена

Рефакторинг структуры делаю по слоям: сначала выношу API в shared/api, потом UI в shared/ui, страницы не трогаю. Каждый шаг - отдельный deploy, форма и заявки работают. Big bang «перепишем всё за выходные» на заказном проекте - red flag.

Что сделать сначала

  1. Разделите api, ui и pages хотя бы на три папки.
  2. Настройте алиас @ в vite.config и tsconfig.
  3. Вынесите повторяющиеся кнопки и инпуты в shared/ui.
  4. Новый экран начинайте с page, не с монолита в App.tsx.
  5. Добавляйте features/widgets только когда появился второй похожий кейс.
  6. Опишите структуру в README для себя и подрядчиков.

Промахи на старте

  • Класть fetch и бизнес-логику прямо в компоненты страниц.
  • Делать одну папку components для всего проекта.
  • Вводить сложную архитектуру на лендинг из трёх экранов.
  • Не настроить алиасы и плодить длинные относительные импорты.
  • Дублировать один и тот же UI вместо shared/ui.

Когда звать на помощь

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

Что почитать ещё

Хотите обсудить похожую задачу? Могу быстро привести фронтенд к понятной структуре без большой миграции.

Обсудить проект →