- заменены все упоминания FSD на SLM Design в 10 файлах документации - обновлены слои features/ и entities/ на business/, infrastructure/, ui/ - обновлены таблицы генерации кода и CLI-примеры - обновлены примеры путей и деревья файлов (naming, project-structure) - исправлен YAML frontmatter в architecture.md (двоеточие без кавычек) - перегенерированы RULES.md и README
81 KiB
NextJS Style Guide
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
Для ассистентов
Полная документация в одном MD файле: https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/RULES.md
Структура документации
Workflow
Что делать и в каком порядке — пошаговые инструкции.
| Раздел | Отвечает на вопрос |
|---|---|
| Начало работы | Что нужно знать перед началом разработки? |
| Создание проекта | Как начать новый проект? |
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
| Добавление страницы | Как добавить новую страницу в проект? |
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
| Стилизация | Как стилизовать компоненты в проекте? |
| Получение данных | Как получать данные с сервера? |
| Управление состоянием | Как работать с состоянием? |
| Локализация | Как добавлять переводы и подключать локализацию? |
Базовые правила
Каким должен быть код — стандарты, не привязанные к конкретной технологии.
| Раздел | Отвечает на вопрос |
|---|---|
| Технологии и библиотеки | Какой стек используем? |
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
| Именование | Как называть файлы, переменные, компоненты, хуки? |
| Документирование | Как писать JSDoc: что документировать, а что нет? |
| Типизация | Как типизировать: type vs interface, any/unknown? |
Прикладные разделы
Как это настроить и использовать — конфигурация, структура и примеры кода для конкретных областей.
| Раздел | Отвечает на вопрос |
|---|---|
| Настройка VS Code | Как настроить редактор для проекта? |
| Структура проекта | Как организованы папки и файлы по SLM? |
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
| Изображения | (не заполнен) |
| SVG-спрайты | (не заполнен) |
| Видео | (не заполнен) |
| API | (не заполнен) |
| Stores | (не заполнен) |
| Хуки | (не заполнен) |
| Шрифты | (не заполнен) |
| Локализация | (не заполнен) |
Workflow
Порядок действий при разработке — от создания проекта до реализации фич.
Создание проекта
Инициализация нового проекта из готового шаблона.
- Создать проект из шаблона:
npx tiged git@gromlab.ru:templates/nextjs.git my-app cd my-app npm install - Проект готов к разработке — стек, структура SLM, конфигурация редактора и шаблоны генерации уже настроены.
Генерация кода
Создание модулей из шаблонов .templates/ вместо ручного создания файлов.
-
Определить тип модуля и соответствующий шаблон:
Модуль Слой Шаблон Компонент ui/componentБизнес-модуль business/moduleВиджет widgets/widgetLayout layouts/layoutЭкран screens/screenСтор stores/store -
Сгенерировать модуль из шаблона.
-
Если подходящего шаблона нет — сначала создать шаблон, затем использовать.
Ручное создание файловой структуры модулей запрещено.
Добавление страницы
Создание нового маршрута: экран + точка входа для роутинга.
- Сгенерировать экран из шаблона
screenвsrc/screens/. - Заполнить экран логикой и стилями.
- Создать
page.tsxв нужном маршрутеsrc/app/.
page.tsx — тонкая обёртка: только metadata и рендер экрана.
Логика, стили и хуки размещаются в экране, не в page.tsx.
Добавление UI-модуля
Создание компонента, бизнес-модуля, виджета или layout.
- Сгенерировать модуль из соответствующего шаблона в целевой слой.
- Заполнить модуль логикой и стилями.
- Дочерние компоненты — генерировать из шаблона
componentв папкуui/внутри родителя.
Дочерние компоненты не экспортируются через index.ts родителя.
Стилизация
Выбор инструмента стилизации по приоритету.
- Использовать Mantine-компоненты и их пропсы.
- Если Mantine не покрывает — использовать CSS-токены
(
--color-*,--space-*,--radius-*). - Если нужна кастомная стилизация — PostCSS Modules.
Инлайн-стили (style), магические значения и глобальные стили
вне app/styles/ запрещены.
Получение данных
Раздел в разработке — SWR, генерация API-клиентов, сокеты.
Управление состоянием
Раздел в разработке — когда создавать стор, что хранить локально и глобально.
Локализация
Раздел в разработке — переводы и i18next.
Технологии и библиотеки
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
Что используем
Стек
React/TypeScript— основной стек для UI и приложения.Next.js— для продуктовых сайтов.
Архитектура
SLM Design— собственная модульная архитектура проекта. Подробнее в разделе Архитектура.
UI компоненты
Mantine UI— базовые UI-компоненты.
Работа с данными (API)
@gromlab/api-codegen— генерация API‑клиентов и типов.SWR— получение, кеширование, ревалидация, дедубликация.SWR (useSWRSubscription)— сокеты, реалтайм подписки.
Store
Zustand— глобальное состояние.
Локализация
i18next (i18n)— локализация всех пользовательских текстов.
Тестирование
Vitest— тестирование.
Стили
PostCSS Modules— изоляция стилей.Mobile First— подход к адаптивной верстке.clsx— конкатенация CSS‑классов.
Генерация
@gromlab/create— шаблонизатор для создания слоёв и других файлов из шаблонов.
Именование
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
Базовые правила
| Что | Рекомендуется |
|---|---|
| Папки | kebab-case |
| Файлы | kebab-case |
| Переменные | camelCase |
| Константы | SCREAMING_SNAKE_CASE |
| Классы | PascalCase |
| React-компоненты | PascalCase |
| Хуки | useSomething |
| CSS классы | camelCase |
| Ключи enum | SCREAMING_SNAKE_CASE |
Именование файлов
Суффикс обозначает роль или тип файла. Пишется в единственном числе.
Формат: name.<suffix>.ts.
Хуки
use-name.hook.ts— файл хука, функция именуетсяuseName
Логика
.store.ts— стор.service.ts— сервис
Типы и контракты
.type.ts— типы и интерфейсы.interface.ts— интерфейсы.enum.ts— enum.dto.ts— внешние DTO.schema.ts— схемы валидации.constant.ts— константы.config.ts— конфигурация
Утилиты
.util.ts— утилиты.helper.ts— вспомогательные функции.lib.ts— библиотечный код
Тесты
.test.ts— тесты.mock.ts— моки
Хорошо
business/
└── auth-by-email/
├── ui/
│ └── login-form.tsx
├── hooks/
│ └── use-auth.hook.ts
├── stores/
│ └── auth.store.ts
├── types/
│ └── auth.type.ts
├── auth-by-email.tsx
└── index.ts
Плохо
business/
└── authByEmail/
├── LoginForm.tsx
├── useAuth.ts
├── authStore.ts
└── index.ts
Булевы значения
- Использовать префиксы
is,has,can,should.
Хорошо
const isReady = true;
const hasAccess = false;
const canSubmit = true;
const shouldRedirect = false;
Плохо
// Плохо: неясное булево значение без префикса.
const ready = true;
const access = false;
const submit = true;
События и обработчики
- Обработчики начинать с
handle. - События и колбэки начинать с
on.
Хорошо
const handleSubmit = () => { ... };
const onSubmit = () => { ... };
Плохо
// Плохо: неочевидное назначение имени.
const submitClick = () => { ... };
Коллекции
- Для массивов использовать имена во множественном числе.
- Для словарей/мап — использовать суффиксы
ById,Map,Dict.
Хорошо
const users = [];
const usersById = {} as Record<string, User>;
const userIds = ['u1', 'u2'];
const ordersMap = new Map<string, Order>();
const featureFlagsDict = { beta: true, legacy: false } as Record<string, boolean>;
Плохо
// Плохо: имя не отражает, что это коллекция.
const user = [];
// Плохо: словарь назван как массив.
const usersMap = [];
// Плохо: по имени непонятно, что это словарь.
const users = {} as Record<string, User>;
SLM Design
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
Преимущества
Вертикальная организация домена
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
Разделение ответственности без перегрузки слоёв
Сервисы приложения (infrastructure/), UI-кит (ui/) и общие ресурсы (shared/) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
Горизонтальная инкапсуляция
Вложенные модули (parts/) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
Колокация по умолчанию
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
Явное разделение каркаса и контента
Каркас группы маршрутов (layouts/) и контент конкретной страницы (screens/) — независимые слои с собственной ответственностью.
Масштабирование через группировку
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
Dependency Injection без фреймворков
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
Происхождение
SLM Design вырос на основе:
- Feature-Sliced Design — слоистая структура, публичный API модуля, направление зависимостей
- Vertical Slice Architecture — модуль как вертикальный срез, содержащий всё необходимое
- Screaming Architecture — структура проекта «кричит» о назначении: открыл
business/auth— видишь авторизацию - Colocation Principle — код живёт рядом с местом использования
Пример структуры проекта
src/
├── app/
│
├── layouts/
│ ├── main/
│ └── dashboard/
│
├── screens/
│ ├── home/
│ ├── products/
│ ├── product-detail/
│ └── about/
│
├── widgets/
│ ├── page-heading/
│ ├── hero-section/
│ └── promo-banner/
│
├── business/
│ ├── auth/
│ ├── catalog/
│ ├── orders/
│ └── chat/
│
├── infrastructure/
│ ├── theme/
│ ├── i18n/
│ ├── backend-api/
│ └── logger/
│
├── ui/
│ ├── button/
│ ├── input/
│ ├── modal/
│ ├── toast/
│ └── dropdown/
│
└── shared/
├── lib/
├── types/
└── styles/
Принципы
- Домен — единое целое. Всё, что относится к домену, живёт в одном модуле.
- Колокация. Код рождается рядом с местом использования и поднимается только при необходимости.
- Зависимости однонаправлены. Импорты только сверху вниз, только через публичный API.
- Архитектура — каркас, не клетка. Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
Слои
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
Определение
Слой — уровень организации кода внутри src/. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.
Группы слоёв
Слои делятся на три группы:
| Группа | Слои | Описание |
|---|---|---|
| Композиция | app, layouts, screens, widgets |
Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
| Ядро | business, infrastructure, ui |
Реализация продукта: бизнес-домены, техсервисы, UI-кит |
| Фундамент | shared |
Общие ресурсы: утилиты, хелперы, стили, конфиги |
Направление зависимостей
Любой импорт между модулями — только через публичный API.
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
layoutsиscreens— параллельные слои, не импортируют друг друга- Модули одного слоя в группе «Композиция» изолированы друг от друга
- Модули одного слоя
infrastructureиuiмогут импортировать друг друга через публичный API - Модули
business— cross-domain зависимости по коду через фабрику,import typeнапрямую - Импорт типов (
import type) в «Ядре» разрешён в обоих направлениях
Слой App
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
В отличие от остальных слоёв, app/ не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
Требования
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
- Никем не импортируется
Слой Layouts
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
src/layouts/
├── main/
├── dashboard/
└── auth/
Требования
- Содержит только модули
- Не содержит бизнес-логику
- Контекстно-зависимые блоки принимает через пропсы от
app, не импортирует напрямую
Слой Screens
Контент конкретной страницы: собирает её из модулей нижних слоёв.
src/screens/
├── home/
├── products/
├── product-detail/
├── about/
└── contacts/
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без index.ts).
src/screens/
├── shop/
│ ├── home/
│ ├── products/
│ ├── product-detail/
│ └── cart/
├── account/
│ ├── profile/
│ ├── settings/
│ └── order-history/
└── info/
├── about/
├── contacts/
└── faq/
Требования
- Содержит только модули
- Не содержит бизнес-логику
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в
widgets/business
Слой Widgets
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
Если блок принадлежит домену — он живёт в business/{area}/, даже если переиспользуется. Если блок нужен только в одном месте — это screens/{name}/parts/ или layouts/{name}/parts/, а не widget.
src/widgets/
├── page-heading/
├── hero-section/
├── onboarding-checklist/
├── promo-banner/
└── error-boundary/
Требования
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в
business/ - Используется в нескольких screens или layouts
Слой Business
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
Слой входит в группу «Ядро». Импортирует infrastructure/, ui/, shared/. Cross-domain зависимости по коду реализуются через фабрику. import type между доменами разрешён напрямую.
Business объединяет то, что в FSD разделено на features и entities: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: types/ — доменная модель, hooks/ и services/ — сценарии и логика, mappers/ — трансформация данных, parts/ — составные блоки.
src/business/
├── auth/
├── catalog/
├── orders/
├── checkout/
└── chat/
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без index.ts).
src/business/
├── commerce/
│ ├── catalog/
│ ├── cart/
│ ├── orders/
│ └── checkout/
└── communication/
├── chat/
└── notifications/
Требования
- Один модуль = один бизнес-домен
- Циклические зависимости между доменами запрещены
- Импорт кода между доменами — через фабрику.
import type— напрямую - Доменные типы (
User,Product) живут здесь, не вshared/
Слой Infrastructure
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
Слой входит в группу «Ядро». Импортирует infrastructure/, ui/, shared/.
Отличие от shared/: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), shared/ — общие ресурсы (утилиты, хелперы, стили, конфиги).
src/infrastructure/
├── theme/
├── i18n/
├── backend-api/
├── maps-api/
├── logger/
├── feature-flags/
└── realtime/
Требования
- Один модуль = один техсервис
- Импортирует
infrastructure/,ui/,shared/
Слой UI
UI-кит без бизнес-логики: button, carousel, toast, modal.
Слой входит в группу «Ядро». Импортирует ui/ и shared/.
Компоненты строятся друг на друге: button использует icon, carousel использует button.
src/ui/
├── button/
├── input/
├── icon/
├── carousel/
├── modal/
├── toast/
├── dropdown/
├── tabs/
└── tooltip/
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (button, icon, input) не импортируют композиции. Композиции (carousel, modal, dropdown) строятся на примитивах.
src/ui/
├── primitives/
│ ├── button/
│ ├── input/
│ ├── icon/
│ └── badge/
└── composites/
├── carousel/
├── modal/
├── dropdown/
├── tabs/
└── tooltip/
Требования
- Не содержит бизнес-логику
- Импортирует только
ui/иshared/
Слой Shared
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
Отличие от infrastructure/: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), shared/ — общие ресурсы (утилиты, хелперы, стили, конфиги).
Отличие от ui/: UI-компоненты (button, carousel, modal) живут в слое ui/, а не здесь.
src/shared/
├── lib/
├── types/
├── styles/
└── sprites/
Требования
- Не имеет runtime-состояния
Модули
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом.
Определение
Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.
Модуль vs компонент
Компонент — один .tsx файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или ui/ сегменте модуля.
Модуль — папка, которая может содержать корневой компонент, сегменты (hooks/, types/, styles/, ui/, parts/ и т.д.) и публичный API (index.ts).
auth/
├── ui/
│ ├── auth-guard.tsx
│ └── logout-button.tsx
├── parts/
│ ├── login-form/
│ ├── registration-form/
│ └── restore-form/
├── hooks/
├── stores/
├── types/
├── auth.tsx # корневой компонент (опционален)
└── index.ts
Структура
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного index.ts с реэкспортом типов.
{module-name}/
├── {module-name}.tsx # корневой компонент (опционален)
├── ui/ # компоненты модуля (только .tsx)
├── parts/ # вложенные модули (со своими сегментами)
├── hooks/ # хуки
├── stores/ # сторы состояния
├── services/ # внешние источники данных
├── mappers/ # трансформация данных между форматами
├── types/ # типы
├── styles/ # стили
├── lib/ # утилиты модуля
├── config/ # константы
└── index.ts # публичный API
Подробное описание каждого сегмента — в разделе Сегменты.
Публичный API
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
// business/auth/index.ts
export type { User, Session } from './types/user.types'
export { useAuth } from './hooks/use-auth.hook'
export { AuthGuard } from './ui/auth-guard'
Импорт в обход index.ts запрещён:
// Плохо
import { validateToken } from '@/business/auth/lib/tokens'
// Хорошо
import { useAuth } from '@/business/auth'
Фабрика
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — import type не является runtime-зависимостью.
Модуль без зависимостей — прямой экспорт:
// business/auth/index.ts
export { useAuth } from './hooks/use-auth'
export { useCurrentUser } from './hooks/use-current-user'
export type { User, Session } from './types'
Модуль с зависимостями — фабрика:
// business/chat/types/deps.ts
import type { User } from '@/business/auth'
export interface ChatDeps {
useCurrentUser: () => User | null
}
// business/chat/index.ts
import type { ChatDeps } from './types/deps'
export function chatFactory(deps: ChatDeps) {
return {
useMessages: (roomId: string) => {
const user = deps.useCurrentUser()
// ...
},
useSendMessage: (roomId: string) => {
const user = deps.useCurrentUser()
return (text: string) => { /* ... */ }
},
useChatRooms: () => {
const user = deps.useCurrentUser()
// ...
},
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
}
}
export type { Message, ChatRoom } from './types'
export type { ChatDeps } from './types/deps'
Использование на странице:
// screens/support/support.tsx
import { useCurrentUser } from '@/business/auth'
import { chatFactory } from '@/business/chat'
const chat = chatFactory({ useCurrentUser })
export function SupportScreen() {
const { useMessages, useSendMessage, ChatBadge } = chat
const messages = useMessages('support')
const sendMessage = useSendMessage('support')
return (
<div>
<ChatBadge count={messages.length} />
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
<MessageInput onSend={sendMessage} />
</div>
)
}
Жизненный цикл
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
- Нужен на одной странице →
screens/{name}/parts/ - Появился в 2+ местах → поднимается по природе:
- абстрактный UI →
ui/ - блок с данными/логикой →
widgets/ - представление бизнес-домена →
business/{area}/parts/
- абстрактный UI →
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
Сегменты
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
Определение
Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.
Обзор
| Сегмент | Содержимое |
|---|---|
ui/ |
Компоненты модуля — только .tsx файлы |
parts/ |
Вложенные модули со своими сегментами |
hooks/ |
React-хуки |
stores/ |
Сторы состояния |
services/ |
Работа с внешними источниками данных |
mappers/ |
Трансформация данных между форматами |
types/ |
TypeScript-типы и интерфейсы |
styles/ |
Стили |
lib/ |
Утилиты и хелперы модуля |
config/ |
Константы и конфигурация |
Сегмент ui/
Компоненты, принадлежащие модулю. Содержит только .tsx файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.
auth/
├── ui/
│ ├── auth-provider.tsx
│ ├── auth-guard.tsx
│ └── logout-button.tsx
├── types/
├── hooks/
└── index.ts
Если компоненту нужны собственные сегменты — это уже не ui/, а parts/.
Сегмент parts/
Вложенные модули со своими сегментами. Каждый элемент parts/ — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.
home/
├── parts/
│ ├── hero-section/
│ │ ├── hero-section.tsx
│ │ ├── styles/
│ │ └── parts/
│ │ └── top-banner/
│ │ └── top-banner.tsx
│ └── features-section/
│ ├── features-section.tsx
│ └── hooks/
├── home.screen.tsx
└── index.ts
Отличие от ui/: элемент parts/ — модуль со своими сегментами. Элемент ui/ — компонент, один .tsx файл.
Вложенность parts/ инкапсулирует область разработки горизонтально: каждый разработчик работает в своём parts/-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
Если вложенный модуль обрастает своими parts/ — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
Сегмент hooks/
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
hooks/
├── use-auth.hook.ts
├── use-session.hook.ts
└── use-permissions.hook.ts
Сегмент stores/
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
stores/
├── auth.store.ts
└── session.store.ts
Сегмент services/
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
services/
├── auth.service.ts
└── token.service.ts
Сегмент mappers/
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
mappers/
├── map-user.ts
├── map-product.ts
└── map-order-to-dto.ts
Сегмент types/
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
types/
├── user.type.ts
└── session.type.ts
Сегмент styles/
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
styles/
├── auth.module.css
└── login-form.module.css
Сегмент lib/
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
lib/
├── validate-email.ts
└── format-phone.ts
Отличие от shared/lib/: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в shared/lib/.
Сегмент config/
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
config/
├── routes.ts
└── constants.ts
Стиль кода
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
Отступы
- 2 пробела (не табы).
Длина строк
- Ориентироваться на 100 символов, но превышение допустимо, если строка читается легко.
- Переносить выражение на новые строки, когда строка становится плохо читаемой.
- Не переносить строку внутри строковых литералов без необходимости.
Хорошо
const config = createRequestConfig(
endpoint,
{
headers: {
'X-Request-Id': requestId,
'X-User-Id': userId,
},
params: {
page,
pageSize,
sort: 'createdAt',
},
},
timeoutMs,
);
Плохо
// Плохо: длинная строка с вложенными структурами плохо читается.
const config = createRequestConfig(endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId }, params: { page, pageSize, sort: 'createdAt' } }, timeoutMs);
Кавычки
- В JavaScript/TypeScript использовать одинарные кавычки.
- В JSX/TSX для атрибутов использовать двойные кавычки.
- Шаблонные строки использовать только при интерполяции или многострочном тексте.
Хорошо
const label = 'Сохранить';
const title = `Привет, ${name}`;
<input type="text" placeholder="Введите имя" />
Плохо
// Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки.
const label = "Сохранить";
const title = 'Привет, ' + name;
// Плохо: одинарные кавычки в JSX-атрибутах.
<input type='text' placeholder='Введите имя' />
Точки с запятой и запятые
- Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным.
- В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна.
Импорты
- В именованных импортах использовать пробелы внутри фигурных скобок.
- Типы импортировать через
import type. defaultэкспорт избегать, использовать именованные.defaultимпорт допустим (например, стили CSS Modules, сторонние библиотеки).- Избегать импорта всего модуля через
*.
Хорошо
import { MyComponent } from 'MyComponent';
import type { User } from '../model/types';
import styles from './styles/button.module.css';
Плохо
// Плохо: отсутствие пробелов в именованном импорте.
import type {User} from '../model/types';
// Плохо: default экспорт.
export default MyComponent;
Ранние возвраты (early return)
- Использовать ранние возвраты для упрощения чтения.
- Избегать
elseпослеreturn.
Хорошо
const getName = (user?: { name: string }) => {
if (!user) {
return 'Гость';
}
return user.name;
};
Плохо
// Плохо: лишний else после return усложняет чтение.
const getName = (user?: { name: string }) => {
if (user) {
return user.name;
} else {
return 'Гость';
}
};
Форматирование объектов и массивов
- В многострочных объектах каждое свойство на новой строке.
- В многострочных массивах каждый элемент на новой строке.
- Объекты и массивы можно писать в одну строку, если длина строки не превышает 100 символов.
- В однострочных объектах и массивах использовать пробелы после запятых.
Хорошо
const roles = ['admin', 'editor', 'viewer'];
const options = { id: 1, name: 'User' };
const config = {
url: '/api/users',
method: 'GET',
params: { page: 1, pageSize: 20 },
};
Плохо
// Плохо: нет пробелов после запятых и объект слишком длинный для одной строки.
const roles = ['admin','editor','viewer'];
const options = { id: 1,name: 'User' };
const config = { url: '/api/users', method: 'GET', params: { page: 1, pageSize: 20 } };
Документирование
Этот раздел описывает правила документирования кода: когда и как писать комментарии к компонентам, функциям, типам и интерфейсам.
Общие правила
- Документировать публичные функции, компоненты, типы, интерфейсы и enum.
- Не документировать очевидное — если название говорит само за себя, комментарий не нужен.
- Не документировать параметры, возвращаемые значения и типы пропсов — они видны из сигнатуры.
- Описание через пользу и назначение, а не через внутреннюю реализацию.
- Описание завершается точкой.
Функции
Для документирования функций используется шаблон. Описание механики опционально — добавляется когда логика нетривиальна.
Шаблон
/**
* <Что делает функция в 1 строке>.
*
* <Опционально: описание сложной механики или важных нюансов>.
*/
Хорошо
/**
* Форматирует цену с символом валюты.
*/
export const formatPrice = (value: number): string => { ... }
/**
* Рекурсивно собирает дерево категорий из плоского списка.
*
* Группирует элементы по parentId, начиная с корневых (parentId = null).
* Категории без родителя попадают в корень дерева.
*/
export const buildCategoryTree = (categories: Category[]): CategoryTree[] => { ... }
Плохо
// Плохо: дублирует сигнатуру.
/**
* @param value - число
* @returns строка с ценой
*/
Компоненты
Компонент описывает своё назначение и сценарии применения — это помогает понять, когда и где его использовать, без необходимости читать реализацию.
Шаблон
/**
* <Назначение компонента в 1 строке>.
*
* Используется для:
* - <сценарий 1>
* - <сценарий 2>
* - <сценарий 3>
*/
Хорошо
/**
* Контейнер с адаптивной максимальной шириной.
*
* Используется для:
* - обёртки контента страниц с ограничением ширины
* - центрирования блоков в лейауте
*/
export const Container = (props: ContainerProps) => { ... }
Плохо
// Плохо: описывает реализацию, а не назначение.
/**
* Рендерит div с className и htmlAttr.
*/
// Плохо: нет описания вообще.
export const Container = (props: ContainerProps) => { ... }
Типы, интерфейсы, enum
Документируются назначение сущности и каждое её поле.
Хорошо
/**
* Фильтры списка задач.
*/
export enum TodoFilter {
/** Все задачи. */
ALL = 'all',
/** Только активные. */
ACTIVE = 'active',
/** Только завершённые. */
COMPLETED = 'completed',
}
/**
* Задача пользователя.
*/
export interface TodoItem {
/** Уникальный идентификатор задачи. */
id: string;
/** Текст задачи. */
text: string;
/** Статус выполнения. */
completed: boolean;
}
Плохо
// Плохо: описывает очевидное.
export interface TodoItem {
/** id — это id */
id: string;
}
Типизация
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с any/unknown.
Общие правила
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
- Предпочитать
typeдля описания сущностей иinterfaceдля расширяемых контрактов. - Избегать
anyиunknownбез необходимости. - Не использовать
ts-ignore, кроме крайних случаев с явным комментарием причины.
Функции
- Для публичных функций указывать возвращаемый тип.
- Не полагаться на неявный вывод для важных API.
Хорошо
export const formatPrice = (value: number): string => {
return `${value} ₽`;
};
Плохо
// Плохо: нет явного возвращаемого типа.
export const formatPrice = (value: number) => {
return `${value} ₽`;
};
Работа с any/unknown
anyиспользовать только для временных заглушек.unknownсужать через проверки перед использованием.
Хорошо
const parse = (value: unknown): string => {
if (typeof value === 'string') {
return value;
}
return '';
};
Плохо
// Плохо: any отключает проверку типов.
const parse = (value: any) => value;
Структура проекта
Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
Корень репозитория
project-root/
├── .templates/ # Шаблоны для генерации модулей
├── .vscode/ # Настройки и рекомендуемые расширения VS Code
├── public/ # Статика, доступная по прямому URL
├── src/ # Исходный код приложения
├── .env.example # Переменные окружения проекта (шаблон)
├── .env # Переменные окружения проекта (не коммитить)
├── .gitignore
├── AGENTS.md # Инструкции для AI-агентов
├── biome.json # Линтер и форматтер (вместо ESLint + Prettier)
├── next.config.ts # Конфигурация Next.js
├── package.json # Зависимости и скрипты
├── postcss.config.mjs # Конфигурация PostCSS
└── tsconfig.json # Конфигурация TypeScript
Папка public/
Хранит статические файлы, которые отдаются по прямому URL без обработки сборщиком:
public/
└── og-image.png
Компоненты, стили и другой исходный код здесь не размещаются.
Папка src/
src/
├── app/ # Роутинг Next.js, провайдеры, глобальные стили
├── layouts/ # Каркасы страниц (header, footer, sidebar)
├── screens/ # Контент конкретной страницы
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
├── business/ # Бизнес-домены (auth, catalog, orders)
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
├── ui/ # UI-кит без бизнес-логики
└── shared/ # Общие ресурсы (утилиты, типы, стили)
Принципы организации слоёв описаны в разделе Архитектура.
Папка app/
Точка входа приложения: инициализация (провайдеры, глобальные стили) и файловый роутинг Next.js (layout.tsx, page.tsx, route-сегменты).
src/app/
├── providers/ # Провайдеры приложения
├── styles/ # Глобальные стили
├── layout.tsx # Корневой layout
└── page.tsx # Главная страница
Папка .templates/
Содержит шаблоны для генерации кода. Каждый подкаталог — шаблон отдельного типа модуля:
.templates/
├── component/ # Шаблон компонента
├── screen/ # Шаблон экрана
├── layout/ # Шаблон layout
├── widget/ # Шаблон виджета
├── module/ # Шаблон бизнес-модуля
└── store/ # Шаблон стора
Подробнее о генерации описано в разделе Шаблоны и генерация кода.
Конфигурационные файлы
| Файл | Назначение |
|---|---|
next.config.ts |
Настройки Next.js: редиректы, переменные окружения, webpack |
tsconfig.json |
Настройки TypeScript: пути, строгость, таргет |
biome.json |
Правила линтера и форматтера Biome |
postcss.config.mjs |
Подключение PostCSS-плагинов (CSS Modules, custom media) |
package.json |
Зависимости, версии, npm-скрипты |
AGENTS.md |
Инструкции для AI-агентов, работающих в проекте |
Переменные окружения
.env— переменные окружения проекта, запрещено коммитить.env.example— шаблон, коммитится в репозиторий
Переменные с префиксом NEXT_PUBLIC_ доступны в клиентском коде. Остальные доступны только на сервере.
Компоненты
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от shared/ui до screens.
Архитектурные слои и их назначение описаны в разделе Архитектура.
Правила организации
- Один компонент — один файл.
- Компонент не содержит бизнес-логики — логика и сайд-эффекты выносятся в хуки или сторы.
- Дочерние компоненты размещаются в сегменте
ui/и подчиняются тем же правилам структуры. - Публичный API модуля — только
index.ts. Прямые импорты внутренних файлов запрещены.
Базовая структура компонента
Минимальный набор файлов: компонент, стили, типы и публичный экспорт.
container/
├── styles/
│ └── container.module.css
├── types/
│ └── container.type.ts
├── container.tsx
└── index.ts
Именования
- Имя корневого css класса всегда
.root - Тип пропсов именуется
{ComponentName}Props. - Тип пользовательских параметров именуется
{ComponentName}Params.
Типизация
Структура типов компонента показана в примере. Ниже — обоснования ключевых решений.
typeвместоinterface— гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.- Без
FC— неявно добавляетchildren, усложняет дженерики, не даёт преимуществ перед аннотацией параметра. - Типы в
types/, а не в.tsx— предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность:.tsxдля рендера,.type.tsдля данных. - Без возвращаемого типа — TypeScript выводит из JSX. Осознанное исключение из базового правила.
Реализация
- Пропсы деструктурируются в теле компонента, не в параметрах.
- Порядок: пользовательские → системные (
children,className) →...htmlAttr. classNameобъединяется с корневым классом черезcl():cl(styles.root, className)....htmlAttrпрокидывается на корневой элемент.
Пример
container/types/container.type.ts
import type { HTMLAttributes } from 'react'
/**
* Параметры компонента Container.
*/
export type ContainerParams = {}
/** HTML-атрибуты корневого элемента. */
type RootAttrs = HTMLAttributes<HTMLDivElement>
export type ContainerProps = RootAttrs & ContainerParams
container/styles/container.module.css
.root {
max-width: var(--content-width);
margin: 0 auto;
padding: 0 var(--spacing-4);
}
container/container.tsx
import cl from 'clsx'
import type { ContainerProps } from './types/container.type'
import styles from './styles/container.module.css'
/**
* Контейнер с адаптивной максимальной шириной.
*
* Используется для:
* - обёртки контента страниц с ограничением ширины
* - центрирования блоков в лейауте
*/
export const Container = (props: ContainerProps) => {
const { children, className, ...htmlAttr } = props
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{children}
</div>
)
}
container/index.ts
export { Container } from './container'
Файлы роутинга
Правила для специальных файлов App Router (page.tsx, layout.tsx, error.tsx, not-found.tsx и др.) — чем наш подход отличается от дефолтного.
Организация
page.tsx— тонкий файл: толькоmetadataи рендер экрана. Логика, стили и зависимости живут в экране, не вpage.tsx.error.tsxиnot-found.tsxделегируют разметку экранам по тому же принципу.layout.tsx— точка подключения провайдеров и глобальных стилей. Вёрстка layout-обёрток выносится в слойlayouts/.- Стили в файлах роутинга не используются — стилизация только внутри вызываемых компонентов.
Реализация
- Каждый
page.tsxэкспортируетmetadataсtitle— он подставляется в шаблон корневого layout (%s | App). - Корневой
layout.tsxзадаётmetadataсtitle.template,description,metadataBaseи OpenGraph-настройками.
Примеры
src/app/profile/[id]/page.tsx
import type { Metadata } from 'next'
import { ProfileScreen } from '@/screens/profile'
export const metadata: Metadata = {
title: 'Профиль',
description: 'Страница профиля пользователя',
}
type ProfilePageProps = {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return <ProfileScreen id={id} />
}
src/app/error.tsx
'use client'
import { ErrorScreen } from '@/screens/error'
type ErrorPageProps = {
error: Error & { digest?: string }
reset: () => void
}
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
return <ErrorScreen error={error} reset={reset} />
}
export default ErrorPage
::: v-pre
Шаблоны и генерация кода
Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI.
Структура шаблонов
Все шаблоны лежат в .templates/ в корне проекта. Каждая папка — отдельный шаблон.
.templates/
├── component/ # шаблон компонента
│ └── {{name.kebabCase}}/
│ ├── styles/
│ │ └── {{name.kebabCase}}.module.css
│ ├── types/
│ │ └── {{name.kebabCase}}.type.ts
│ ├── {{name.kebabCase}}.tsx
│ └── index.ts
└── store/ # шаблон Zustand стора
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.store.ts
├── {{name.kebabCase}}.type.ts
└── index.ts
Синтаксис шаблонов
Переменные работают в именах файлов/папок и внутри файлов. Базовая переменная — name.
{{variable}}
Модификаторы меняют регистр и формат записи:
{{name.pascalCase}} → MyButton
{{name.camelCase}} → myButton
{{name.kebabCase}} → my-button
{{name.snakeCase}} → my_button
{{name.screamingSnakeCase}} → MY_BUTTON
Как создать новый шаблон
- Создать папку в
.templates/с именем шаблона (напримерhook). - Внутри разместить файлы и папки, используя
{{name}}и модификаторы в именах и содержимом. - Шаблон сразу доступен и в расширении VS Code, и в CLI.
Пример — создание шаблона для хука:
.templates/
└── hook/
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.hook.ts
└── index.ts
// .templates/hook/{{name.kebabCase}}.hook.ts
export const {{name.camelCase}} = () => {
}
// .templates/hook/index.ts
export { {{name.camelCase}} } from './{{name.kebabCase}}.hook'
Примеры шаблонов
Шаблон компонента
// .templates/component/index.ts
export { {{name.pascalCase}} } from './{{name.kebabCase}}'
// .templates/component/types/{{name.kebabCase}}.type.ts
import type { HTMLAttributes } from 'react'
/**
* Параметры {{name.pascalCase}}.
*/
export type {{name.pascalCase}}Params = {}
/** HTML-атрибуты корневого элемента. */
type RootAttrs = HTMLAttributes<HTMLDivElement>
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
// .templates/component/{{name.kebabCase}}.tsx
import cl from 'clsx'
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
import styles from './styles/{{name.kebabCase}}.module.css'
/**
* {{name.pascalCase}}.
*/
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
const { children, className, ...htmlAttr } = props
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{children}
</div>
)
}
/* .templates/component/styles/{{name.kebabCase}}.module.css */
.root {
}
Генерация через VS Code
Template File Generator | gromlab (Marketplace, Open VSX) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
- ПКМ на целевой папке в проводнике VS Code.
- Generate from template → выбрать шаблон.
- Ввести имя (например
button) — расширение подставит его во все переменные{{name}}.
Генерация через CLI
@gromlab/create — CLI для генерации из тех же шаблонов. Используется через npx, глобальная установка не требуется.
npx @gromlab/create <шаблон> <имя> <путь>
| Команда | Что создаёт |
|---|---|
npx @gromlab/create component button src/shared/ui |
Компонент |
npx @gromlab/create module auth src/business |
Бизнес-модуль |
npx @gromlab/create widget header src/widgets |
Виджет |
npx @gromlab/create layout admin src/layouts |
Layout |
npx @gromlab/create store auth src/business/auth/stores |
Стор |
:::
Стили
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
Общие правила
- Только PostCSS и CSS Modules для кастомной стилизации.
- Подход Mobile First — стили пишутся от мобильных к десктопу.
- Именование классов —
camelCase(.root,.buttonNext,.itemTitle). - Модификаторы — отдельный класс с
_, применяется через&._modifier.
Хорошо
.submitButton {
padding: 8px 16px;
&._disabled {
opacity: 0.5;
}
}
Плохо
/* Плохо: kebab-case и вложенный элемент вместо отдельного класса. */
.submit-button {
padding: 8px 16px;
&__icon {
margin-right: 8px;
}
}
Вложенность
- Вложенность селекторов запрещена.
- Исключения:
- Псевдоклассы:
&:hover,&:active,&:focus,&:disabledи т.д. - Псевдоэлементы:
&::before,&::after. - Медиа-запросы:
@media. - Модификаторы:
&._active,&._disabled.
- Псевдоклассы:
- Каждый вложенный блок отделяется пустой строкой от предыдущих свойств.
Хорошо
.card {
padding: 16px;
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&::after {
content: '';
display: block;
}
&._highlighted {
border-color: var(--color-primary);
}
@media (--md) {
padding: 24px;
}
}
.cardTitle {
font-size: 16px;
@media (--md) {
font-size: 20px;
}
}
Плохо
/* Плохо: вложенность селекторов, нет пустых строк между блоками. */
.card {
padding: 16px;
.cardTitle {
font-size: 16px;
}
&:hover {
background-color: var(--color-bg-hover);
}
}
Медиа-запросы
- Только Custom Media Queries:
@media (--md) {}. - Запрещены произвольные breakpoints:
@media (min-width: 768px). @mediaпишется только внутри селектора.- Запрещено писать
@mediaна верхнем уровне с селекторами внутри.
Хорошо
.sidebar {
display: none;
@media (--md) {
display: block;
}
}
.sidebarTitle {
font-size: 14px;
@media (--md) {
font-size: 18px;
}
}
Плохо
/* Плохо: @media на верхнем уровне с селекторами внутри. */
@media (--md) {
.sidebar {
display: block;
}
.sidebarTitle {
font-size: 18px;
}
}
/* Плохо: произвольный breakpoint вместо custom media. */
.sidebar {
@media (min-width: 992px) {
display: block;
}
}
CSS-переменные
- Цвета (
--color-*), отступы (--space-*), скругления (--radius-*) определяются вapp/styles/variables.cssчерез:root. - Файл переменных подключается один раз в корневом layout/entry point — после этого переменные доступны глобально через каскад.
- Не дублировать магические значения в компонентах.
Хорошо
/* app/styles/variables.css */
:root {
--color-primary: #3b82f6;
--color-bg: #ffffff;
--color-bg-hover: #f5f5f5;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--radius-1: 4px;
--radius-2: 8px;
}
/* компонент */
.card {
padding: var(--space-3);
border-radius: var(--radius-2);
background-color: var(--color-bg);
}
Плохо
/* Плохо: магические значения вместо переменных. */
.card {
padding: 12px;
border-radius: 8px;
background-color: #ffffff;
}
Custom Media
- Breakpoints определяются через Custom Media Queries в
app/styles/media.css. - Custom media подключаются глобально через конфиг PostCSS (плагин
postcss-custom-media) — не импортировать в файлы стилей.
/* app/styles/media.css */
@custom-media --sm (min-width: 36em);
@custom-media --md (min-width: 62em);
@custom-media --lg (min-width: 82em);
Импорт стилей
- Стили компонента импортируются только внутри своего компонента.
- Запрещено импортировать стили одного компонента в другой.
- Custom media не импортируются в файлы стилей — они подключаются глобально через конфиг PostCSS.
Форматирование
- Пустая строка между селекторами верхнего уровня.
- Пустая строка перед каждым вложенным блоком (медиа, псевдокласс, модификатор).
Хорошо
.userBar {
display: none;
color: var(--color-text);
@media (--md) {
display: flex;
}
}
.userBarButton {
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&._active {
background-color: var(--color-primary);
}
}
Плохо
/* Плохо: нет пустых строк между селекторами и вложенными блоками. */
.userBar {
display: none;
color: var(--color-text);
@media (--md) {
display: flex;
}
}
.userBarButton {
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&._active {
background-color: var(--color-primary);
}
}
Единицы измерения
px— основная единица измерения.- Остальные (
em,rem,%,vh/vw) — допускаются по необходимости дизайна.
Порядок CSS-свойств
В стилях рекомендуется придерживаться логического порядка свойств:
- Позиционирование (
position,top,left,z-index). - Блочная модель (
display,width,height,margin,padding). - Оформление (
background,border,box-shadow,border-radius). - Текст (
font,color,text-align,line-height). - Прочее (
transition,animation,opacity,cursor).
Комментарии
- Желательно не писать комментарии в CSS.
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
SVG-спрайты
Настройка VS Code
Каждый проект содержит папку .vscode/ с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями.
Структура .vscode/
.vscode/
├── extensions.json # Рекомендуемые расширения
└── settings.json # Настройки редактора для проекта
Оба файла коммитятся в репозиторий.
Расширения
Файл .vscode/extensions.json определяет список расширений, которые VS Code предложит установить при открытии проекта.
// .vscode/extensions.json
{
"recommendations": [
"biomejs.biome",
"MyTemplateGenerator.mytemplategenerator",
"csstools.postcss"
]
}
| Расширение | Назначение |
|---|---|
| Biome | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
| Template File Generator | gromlab (Marketplace, Open VSX) | Генерация файлов и папок из шаблонов .templates/ через контекстное меню |
| PostCSS Language Support | Подсветка синтаксиса и автодополнение для PostCSS (@custom-media, @nest и др.) |
Зачем это нужно
- Новый участник команды получает все нужные расширения одним кликом.
- Нет разночтений: все используют одинаковый форматтер и линтер.
- Расширения привязаны к проекту, а не к конкретному разработчику.
Настройки редактора
Файл .vscode/settings.json переопределяет пользовательские настройки VS Code на уровне проекта.
// .vscode/settings.json
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"files.associations": {
"*.css": "postcss"
}
}
Разбор настроек
| Настройка | Значение | Что делает |
|---|---|---|
editor.defaultFormatter |
biomejs.biome |
Biome используется как единственный форматтер для всех файлов |
editor.formatOnSave |
true |
Код автоматически форматируется при каждом сохранении |
codeActionsOnSave.source.fixAll.biome |
explicit |
Biome автоматически применяет безопасные исправления при сохранении |
codeActionsOnSave.source.organizeImports.biome |
explicit |
Импорты сортируются и группируются автоматически при сохранении |
files.associations |
"*.css": "postcss" |
Все CSS-файлы открываются с подсветкой PostCSS вместо стандартного CSS |
Зачем это нужно
- Единый стиль кода -- форматирование происходит автоматически, невозможно закоммитить неформатированный код.
- Автофикс при сохранении -- распространённые ошибки линтинга исправляются без ручного вмешательства.
- Сортировка импортов -- импорты всегда в одном порядке, без конфликтов при мерже.
- PostCSS-подсветка -- кастомные at-правила (
@custom-media,@define-mixin) подсвечиваются корректно, а не как ошибки.
Что не должно быть в .vscode/
Не коммитятся файлы, специфичные для конкретного разработчика:
- Не коммитить: отладочные конфигурации с локальными путями, персональные сниппеты, настройки тем оформления.
- Коммитить: только
extensions.jsonиsettings.jsonс общими для команды настройками.