Files
nextjs-style-guide/generated/ru/RULES.md
S.Gromov b37eb75542
Some checks failed
CI/CD Pipeline / deploy (push) Blocked by required conditions
CI/CD Pipeline / docker (push) Failing after 12m36s
sync
2026-03-28 21:15:15 +03:00

51 KiB
Raw Blame History

NextJS Style Guide

Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.

Для ассистентов

Полная документация в одном MD файле: https://gromlab.ru/docs/frontend-style-guide/raw/branch/main/generated/ru/RULES.md

Структура документации

Workflow

Что делать в конкретной ситуации — пошаговые инструкции.

Раздел Отвечает на вопрос
Начало работы Какие инструменты установить перед началом разработки?
Создание приложения Как создать новый проект, откуда взять шаблон?
Создание страниц Как добавить страницу: роутинг и экран?
Создание компонентов Как генерировать компоненты через шаблоны?
Стилизация Чем стилизовать: Mantine, токены или PostCSS?
Работа с данными Как получать данные: SWR, кодген, сокеты?
Управление состоянием Когда и как создавать стор (Zustand)?
Локализация Как добавлять переводы и работать с i18next?

Базовые правила

Каким должен быть код — стандарты, не привязанные к конкретной технологии.

Раздел Отвечает на вопрос
Технологии и библиотеки Какой стек используем?
Архитектура Как устроены слои FSD, зависимости, публичный API?
Стиль кода Как оформлять код: отступы, кавычки, импорты, early return?
Именование Как называть файлы, переменные, компоненты, хуки?
Документирование Как писать JSDoc: что документировать, а что нет?
Типизация Как типизировать: type vs interface, any/unknown, FC?

Прикладные разделы

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

Раздел Отвечает на вопрос
Структура проекта Как организованы папки и файлы по FSD?
Компоненты Как устроен компонент: файлы, пропсы, clsx, FC?
Шаблоны и генерация Как работают шаблоны: синтаксис, переменные, модификаторы?
Стили Как писать CSS: PostCSS Modules, вложенность, медиа, токены?
Изображения (не заполнен)
SVG-спрайты (не заполнен)
Видео (не заполнен)
API (не заполнен)
Stores (не заполнен)
Хуки (не заполнен)
Шрифты (не заполнен)
Локализация (не заполнен)

Начало работы

Что нужно установить и настроить перед началом разработки.

Инструменты

CLI

Инструмент Установка Назначение
@gromlab/create npm i -g @gromlab/create Генерация файлов и папок по шаблонам

VS Code

Расширения

Каждый проект должен содержать файл .vscode/extensions.json — VS Code автоматически предложит установить нужные расширения при открытии проекта.

// .vscode/extensions.json
{
  "recommendations": [
    "biomejs.biome",
    "MyTemplateGenerator.mytemplategenerator",
    "csstools.postcss"
  ]
}
Расширение Назначение
MyTemplateGenerator Генерация файлов и папок из шаблонов через UI
Biome Линтинг и форматирование кода
PostCSS Language Support Подсветка и автодополнение PostCSS

Настройки

Каждый проект должен содержать файл .vscode/settings.json с базовой конфигурацией редактора.

// .vscode/settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "files.associations": {
    "*.css": "postcss"
  }
}
Настройка Что делает
editor.defaultFormatter Biome как форматтер по умолчанию
editor.formatOnSave Автоформатирование при сохранении
codeActionsOnSave Автофикс и сортировка импортов при сохранении
files.associations CSS-файлы открываются с подсветкой PostCSS

Создание приложения

Как создать новое приложение: выбор шаблона проекта и инициализация.

Создание страниц

Паттерн создания страниц: роутинг (page.tsx) и экран (screen).

Создание компонентов

Генерация компонентов через шаблоны, работа с дочерними компонентами.

Стилизация

Приоритет инструментов стилизации и правила их применения.

Работа с данными

Как получать данные: SWR, кодген API-клиентов, сокеты.

Управление состоянием

Когда и как создавать стор (Zustand), что хранить локально и глобально.

Локализация

Как добавлять переводы и работать с i18next.

Технологии и библиотеки

Базовый стек технологий и библиотек, на который опираются проекты и примеры в документации.

Что используем

Стек

  • React/TypeScript — основной стек для UI и приложения.
  • Next.js — для продуктовых сайтов.

Архитектура

  • FSD (Feature-Sliced Design) — структура проекта и границы модулей.

UI компоненты

  • Mantine UI — базовые UI-компоненты.

Fetch (API)

  • @gromlab/api-codegen — генерация APIклиентов и типов.
  • SWR — получение, кеширование, ревалидация, дедубликация.
  • SWR (useSWRSubscription) - сокеты, реалтайм подписки.

Store

  • Zustand — глобальное состояние.

Локализация

  • i18next (i18n) — локализация всех пользовательских текстов.

Тестирование

  • Vitest — тестирование.

Стили

  • PostCSS Modules — изоляция стилей.
  • Mobile First — подход к адаптивной верстке.
  • clsx — конкатенация CSSклассов.

Генерация

  • @gromlab/create — шаблонизатор для создания слоёв и других файлов из шаблонов.

Архитектура

Архитектура построена на FSD (FeatureSliced Design) и строгих границах модулей. Цель — разделить ответственность, упростить сопровождение и контроль зависимостей.

Принципы

  • Разделять UI, бизнес-логику и инфраструктуру.
  • Держать зависимости однонаправленными.
  • Открывать наружу только публичный API модулей.
  • Не допускать циклических зависимостей.

Слои (FSD)

  • app — инициализация приложения: провайдеры, глобальные стили. В Next.js эта же папка app/ дополнительно содержит системные файлы роутинга (layout.tsx, page.tsx).
  • screens — UI-компоненты страниц. Каждый экран — отдельный компонент, который собирает виджеты и фичи конкретной страницы. Роутинг только использует эти компоненты — он не является частью слоя screens. В Next.js файлы page.tsx остаются тонкими: импортируют экран и рендерят его.
  • layouts — каркас и шаблоны страниц.
  • widgets — крупные блоки интерфейса, собирающие несколько сценариев.
  • features — отдельные пользовательские действия и сценарии.
  • entities — бизнес-сущности и их модель.
  • shared — переиспользуемая инфраструктура, утилиты и базовые UIкомпоненты.

Модули (FSD)

  • Модуль — это отдельная папка в слоях screens, layouts, widgets, features, entities, которая реализует один сценарий/блок. В корне модуля лежит главный файл (*.screen.tsx, *.layout.tsx, *.widget.tsx, *.feature.tsx, *.entity.tsx) и публичный API (index.ts).
  • Внутри модуля используются подпапки (по необходимости):
    • ui/ — дочерние UIкомпоненты модуля.
    • model/ — состояние и бизнес‑логика модуля.
    • styles/ — локальные стили модуля.
    • helpers/ — локальные хелперы.
    • lib/ — утилиты модуля.
    • api/ — APIвызовы модуля.

Правила зависимостей

  • Допустимые импорты идут сверху вниз: app → screens → layouts → widgets → features → entities → shared.
  • Импорты между слоями — через публичный API.
  • Внутри одного слоя — относительные импорты.

Публичный API модулей

  • Каждый модуль экспортирует наружу только то, что нужно другим слоям.
  • Внешние импорты идут только через index‑файл модуля.
  • Внутренние файлы не импортируются напрямую извне.

Границы ответственности

  • Бизнес‑логика не размещается в UIкомпонентах.
  • UIкомпоненты должны быть максимально простыми и предсказуемыми.
  • Связь между независимыми сценариями поднимается на уровень выше.

Типовые ошибки

  • Импорт из более высокого слоя в более низкий.
  • Смешивание логики нескольких слоёв в одном модуле.
  • Прямые импорты внутренних файлов, минуя публичный API.

Стиль кода

Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.

Отступы

  • 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 } };

Именование

Именование должно быть предсказуемым, коротким и отражать смысл сущности.

Базовые правила

Что Рекомендуется
Папки kebab-case
Файлы kebab-case
Переменные camelCase
Константы SCREAMING_SNAKE_CASE
Классы PascalCase
React-компоненты PascalCase
Хуки useSomething
CSS классы camelCase

Архитектурный неймспейс

Соглашение о суффиксах, которые обозначают слой (уровень абстракции), роль или тип файла.

  • Суффиксы используются для обозначения слоя, роли или типа файла.
  • Суффиксы всегда пишутся в единственном числе.
  • Формат имени: name.<suffix>.ts или name.<suffix>.tsx.

UI и слои FSD

  • .screen.tsx — экран
  • .layout.tsx — layout
  • .widget.tsx — виджет
  • .feature.tsx — UI фичи
  • .entity.tsx — UI сущности
  • .ui.tsx — UIкомпонент

Логика и модель

  • .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 / .test.tsx
  • .mock.ts

Хорошо

src/
├── screens/
│   └── main/
│       ├── main.screen.tsx
│       └── index.ts
├── features/
│   └── auth-by-email/
│       ├── ui/
│       │   └── login-form.ui.tsx
│       ├── auth-by-email.feature.tsx
│       └── index.ts
└── shared/
    └── ui/
        └── icon/
            ├── styles/
            │   └── icon.module.css
            ├── types/
            │   └── icon.interface.ts
            ├── icon.ui.tsx
            └── index.ts

Плохо

// Плохо: нет единых правил для слоёв и публичных файлов.
src/
├── screens/
│   └── Main/
│       └── Main.tsx
└── features/
    └── authByEmail/
        └── login-form.tsx

Булевы значения

  • Использовать префиксы 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>;

Документирование

Документирование должно помогать понять назначение сущности, а не дублировать её типы или очевидные детали.

Правила

  • Документировать только назначение функций, компонентов, типов, интерфейсов и enum.
  • Не документировать параметры, возвращаемые значения, типы пропсов и очевидные детали.
  • В интерфейсах, типах и enum описывать только смысл поля или значения.
  • Описание должно быть кратким, информативным и завершаться точкой.

Примеры

Хорошо

/**
 * Список задач пользователя.
 */
export const TodoList = memo(() => { ... });

/**
 * Интерфейс задачи.
 */
export interface TodoItem {
  /** Уникальный идентификатор задачи. */
  id: string;
  /** Текст задачи. */
  text: string;
  /** Статус выполнения задачи. */
  completed: boolean;
}

/**
 * Перечисление фильтров задач.
 */
export enum TodoFilter {
  /** Все задачи. */
  All = 'all',
  /** Только активные задачи. */
  Active = 'active',
  /** Только выполненные задачи. */
  Completed = 'completed',
}

Плохо

// Плохо: дублирование параметров и возвращаемых значений.
/**
 * @param id - идентификатор задачи
 * @returns объект задачи
 */

// Плохо: описание очевидных деталей.
/**
 * id — идентификатор задачи
 * text — текст задачи
 * completed — статус выполнения
 */

Типизация

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

Общие правила

  • Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
  • Предпочитать type для описания сущностей и interface для расширяемых контрактов.
  • Избегать any и unknown без необходимости.
  • Не использовать ts-ignore, кроме крайних случаев с явным комментарием причины.

Типы для компонентов

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

Хорошо

/**
 * Параметры кнопки.
 */
interface ButtonProps extends HTMLAttributes<HTMLDivElement> {
  /** Текст кнопки. */
  label: string;
  /** Обработчик клика по кнопке. */
  onClick: () => void;
}

/**
 * Кнопка с пользовательскими стилями.
 */
export const Button: FC<ButtonProps> = ({ className, label, onClick, ...htmlAttr }) => {
  return (
    <div {...htmlAttr} className={cl(styles.root, className)}>
      button
    </div>
  )
}

Плохо

// Плохо: параметры не типизированы.
export const Button = (props) => (
  <button type="button" onClick={props.onClick}>
    {props.label}
  </button>
);

Функции

  • Для публичных функций указывать возвращаемый тип.
  • Не полагаться на неявный вывод для важных 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;

Структура проекта

Раздел описывает базовую структуру проекта и принципы организации модулей на уровне папок и файлов.

Базовая структура проекта

Слои FSD не зависят от фреймворка. Различается только содержимое app/ — в React SPA это конфигурация роутинга, в Next.js — системные файлы фреймворка (layout.tsx, page.tsx, route-сегменты).

src/
├── app/                  # Инициализация приложения (см. «Слой app/»)
├── screens/              # UI-компоненты страниц
│   └── profile/
│       ├── profile.screen.tsx
│       └── index.ts
├── layouts/              # Общие шаблоны и каркасы страниц
│   └── main-layout/
│       ├── main-layout.layout.tsx
│       └── index.ts
├── widgets/              # Крупные блоки интерфейса
│   └── header/
│       ├── header.widget.tsx
│       └── index.ts
├── features/             # Пользовательские сценарии
│   └── auth-by-email/
│       ├── ui/
│       │   └── login-form.ui.tsx
│       ├── model/
│       │   └── auth-by-email.store.ts
│       ├── auth-by-email.feature.tsx
│       └── index.ts
├── entities/             # Бизнес-сущности
│   └── user/
│       ├── model/
│       │   └── user.store.ts
│       ├── user.entity.tsx
│       └── index.ts
└── shared/               # Общие ресурсы проекта
    ├── ui/               # Повторно используемые UI-элементы
    │   └── icon/
    │       ├── styles/
    │       │   └── icon.module.css
    │       ├── types/
    │       │   └── icon.interface.ts
    │       ├── icon.ui.tsx
    │       └── index.ts
    ├── lib/              # Утилиты и хелперы
    ├── services/         # Общие сервисы и клиенты
    ├── config/           # Общие конфигурации и константы
    └── assets/           # Ресурсы
        ├── images/
        ├── icons/
        ├── fonts/
        └── video/

Слой app/

Общее для обоих вариантов: провайдеры и глобальные стили. Различается только способ организации роутинга.

React SPA

src/app/
├── providers/            # Провайдеры и обёртки приложения
├── routing/              # Конфигурация маршрутов (React Router)
├── styles/               # Глобальные стили, CSS-переменные, custom media
└── index.ts              # Entry point приложения

Next.js (App Router)

src/app/
├── providers/            # Провайдеры и обёртки приложения
├── styles/               # Глобальные стили, CSS-переменные, custom media
├── layout.tsx            # Корневой layout (подключает providers, styles)
├── page.tsx              # Главная страница
└── profile/
    └── page.tsx          # Рендерит ProfileScreen

В Next.js файлы page.tsx остаются тонкими — они только импортируют экран из screens/ и рендерят его. Вся логика, зависимости и стили страницы живут в компоненте экрана, а не в app/.

// src/app/profile/page.tsx
import { ProfileScreen } from '@/screens/profile';

export default function ProfilePage() {
  return <ProfileScreen />;
}

Плохо

// Плохо: слои смешаны, нет понятных границ и публичного API.
src/
├── components/
├── api/
├── styles/
└── user.ts

Правила организации

  • В слоях FSD (features, entities, widgets, screens и т.д.) ui/ используется только для дочерних элементов, которые относятся к модулю и не экспортируются отдельно. Главные компоненты, которые составляют сам слой, держат собственные *.feature.tsx, *.widget.tsx и т. п., а ui/ служит для вспомогательных мелких компонентов.

  • В shared/ui/ хранятся базовые UI-элементы/компоненты, которыми пользуются сразу несколько модулей; в этом случае они экспортируются наружу и не считаются «дочерними» для слоя.

  • Если модуль строится вокруг «главного» компонента (*.feature.tsx, *.screen.tsx, *.widget.tsx), помещайте его в корень модуля и экспортируйте через index.ts. Проверяйте, что ui/ не используется просто как «контейнер» слоя.

  • Каждый слой и модуль хранится в собственной папке.

  • Внутренние реализации разделяются на ui/, model/, styles/, helpers/, lib/, api/.

  • Публичный API модуля объявляется в index.ts.

  • Внутренние файлы не импортируются напрямую извне.

  • Не смешивать ответственность разных слоёв в одном модуле.

Компоненты

Раздел описывает правила создания UIкомпонентов. Эти правила обязательны для всех слоёв FSD: app, screens, layouts, widgets, features, entities, shared.

Базовая структура компонента

Минимальный набор файлов: компонент, стили, типы и публичный экспорт.

container/
├── styles/
│   └── container.module.scss
├── types/
│   └── container.interface.ts
├── container.ui.tsx
└── index.ts

Пример базового компонента

styles/container.module.scss

.root {}

В CSS Modules использование имени класса .root — это общепринятое соглашение (best practice)

types/container.interface.ts

import type { HTMLAttributes } from 'react'

/**
 * Параметры контейнера.
 */
export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {}

Интерфес параметров компонента всегда наследует свойства своего тега: div, button, итд..

container.ui.tsx

import type { FC } from 'react'
import cl from 'clsx'
import type { ContainerProps } from './types/container.interface'
import styles from './styles/container.module.scss'

/**
 * Контейнер с адаптивной максимальной шириной.
 *
 * Используется для:
 *  - ограничения ширины контента
 *  - центрирования содержимого
 *  - построения адаптивной сетки страницы
 */
export const Container: FC<ContainerProps> = ({ className, ...htmlAttr }) => {
  return (
    <div {...htmlAttr} className={cl(styles.root, className)}>
      Container...
    </div>
  )
}
  • Компонент объявляется через const и экспортируется именованно.
  • Пропсы деструктурируются в сигнатуре; если их больше двух — деструктуризацию переносим в тело компонента.
  • Из пропсов отдельно извлекаются className и ...htmlAttr, чтобы корректно объединять классы и прокидывать остальные атрибуты.
  • cl — короткое имя функции для конкатенации CSSклассов.
  • FC<> используется для декларации children.

index.ts

export { Container } from './container.ui'

Шаблоны и генерация кода

Создание компонентов — только через шаблоны. Ручное создание файловой структуры компонента запрещено. Это обеспечивает единообразие каркаса, одинаковые папки и имена файлов, уменьшает ручные ошибки и ускоряет старт работы.

После генерации через @gromlab/create — проверить название компонента/файлов и заполнить описание назначения. Подробный порядок действий и перечень обязательных шаблонов — в разделе «Workflow».

Вложенные (дочерние) компоненты

Если для реализации функционала нужны компоненты, которые используются только внутри текущего компонента, создавайте их как вложенные в папке ui/. Такие компоненты не экспортируются наружу и используются только локально.

Вложенные компоненты подчиняются тем же правилам по структуре, именованию и стилю (включая папку styles/ для их стилей).

Шаблоны генерации кода

Раздел описывает инструменты, синтаксис шаблонов и примеры. Порядок действий при создании модулей и перечень обязательных шаблонов — в разделе «Workflow».

Обязательность

  • Создание типовых модулей — только через шаблоны. Ручное создание файловой структуры модуля запрещено, если для него существует шаблон.
  • Перед созданием нового модуля — проверить наличие подходящего шаблона в .templates/.
  • Если подходящего шаблона нет — сначала создать шаблон, затем использовать его.

Что генерируем

  • Компоненты (screens, layouts, widgets, features, entities).
  • Страницы (nextjs app, pages).
  • Типовые инфраструктурные модули (например, store).

Чем генерируем

VSCode extension

расширение VS Code — создание файлов и папок из шаблонов через UIинтерфейс внутри редактора.

CLI (для агентов)

@gromlab/create — CLI для генерации файлов и папок по шаблонам.

Примеры:

# Создать компонент
create component button

# Создать компонент используя NPX
npx @gromlab/create component button

Структура папок

Все шаблоны лежат в .templates/ в корне проекта.
Каждая папка в .templates/ — это уникальный шаблон.

.templates/                     # корневая папка всех шаблонов
├── component/                  # шаблон компонента
│   └── {{name.kebabCase}}/
│       ├── styles/
│       │   └── {{name.kebabCase}}.module.css
│       ├── types/
│       │   └── {{name.kebabCase}}.interface.ts
│       ├── {{name.kebabCase}}.ui.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

Пример использования в шаблоне:

{{name}}.tsx
{{name.pascalCase}}.tsx
export const {{name.pascalCase}} = () => {
  return <div>{{name}}</div>
}

Шаблон компонента

Структура компонента по шаблону. Создаётся генератором автоматически.

// .templates/component/index.ts
export { {{name.pascalCase}} } from './{{name.kebabCase}}.ui'
// .templates/component/types/{{name.kebabCase}}.interface.ts
import type { HTMLAttributes } from 'react'

/**
 * Параметры {{name.pascalCase}}.
 */
export interface {{name.pascalCase}}Props extends HTMLAttributes<HTMLDivElement> {}
// .templates/component/{{name.kebabCase}}.ui.tsx
import type { FC } from 'react'
import cl from 'clsx'
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.interface'
import styles from './styles/{{name.kebabCase}}.module.css'

/**
 * {{name.pascalCase}}.
 */
export const {{name.pascalCase}}: FC<{{name.pascalCase}}Props> = ({ className, ...htmlAttr }) => {
  return (
    <div {...htmlAttr} className={cl(styles.root, className)}>
      {{name.kebabCase}}
    </div>
  )
}
/* .templates/component/styles/{{name.kebabCase}}.module.css */
.root {

}

Стили

Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.

Приоритет стилизации

Приоритет инструментов стилизации (от высшего к низшему):

  1. Mantine-компоненты и их пропсы — в первую очередь использовать встроенные возможности Mantine.
  2. Глобальные CSS-токены (--color-*, --space-*, --radius-*) — для значений, которые не покрываются Mantine.
  3. PostCSS Module файлы — когда Mantine не покрывает задачу и нужна кастомная стилизация.
  • Инлайн-стили в компонентах запрещены.
  • Произвольные магические значения цветов, отступов и скруглений запрещены — использовать токены.
  • Глобальные стили вне app/styles/ запрещены.

Подробный порядок действий — в разделе «Workflow».

Общие правила

  • Только 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-свойств

В стилях рекомендуется придерживаться логического порядка свойств:

  1. Позиционирование (position, top, left, z-index).
  2. Блочная модель (display, width, height, margin, padding).
  3. Оформление (background, border, box-shadow, border-radius).
  4. Текст (font, color, text-align, line-height).
  5. Прочее (transition, animation, opacity, cursor).

Комментарии

  • Желательно не писать комментарии в CSS.
  • Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.

SVG-спрайты