Files
nextjs-style-guide/generated/ru/RULES.md
S.Gromov b104ca6581
All checks were successful
CI/CD Pipeline / docker (push) Successful in 45s
CI/CD Pipeline / deploy (push) Successful in 7s
docs: рефакторинг документации — workflow, прикладные разделы, генерация RULES.md
- Переработан раздел Workflow: заголовки, описания, порядок разделов
- Добавлены новые разделы: Генерация кода (workflow), Настройка VS Code (applied)
- Убран суффикс .ui.tsx из документации и примеров
- Переработан раздел Структура проекта — только Next.js, без React SPA
- Приоритет стилизации перенесён из applied/styles в workflow/styling
- Убрано дублирование инструментов генерации — единая точка в applied/templates-generation
- Переписан concat-md.js: без внешних зависимостей, мета-якоря для навигации в RULES.md
- Удалена зависимость concat-md
- Обновлена главная страница: названия разделов, URL на RULES.md
- Добавлен AGENTS.md с правилами для агентов
2026-03-29 11:43:23 +03:00

67 KiB
Raw Blame History

NextJS Style Guide

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

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

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

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

Workflow

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

Раздел Отвечает на вопрос
Начало работы Что нужно знать перед началом разработки?
Создание проекта Как начать новый проект?
Генерация кода Какие модули должны генерироваться из шаблонов?
Добавление страницы Как добавить новую страницу в проект?
Добавление UI-модуля Как создать компонент, фичу, виджет, сущность или layout?
Стилизация Как стилизовать компоненты в проекте?
Получение данных Как получать данные с сервера?
Управление состоянием Как работать с состоянием?
Локализация Как добавлять переводы и подключать локализацию?

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

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

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

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

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

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

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

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

Стек проекта

Next.js (App Router), Mantine, Zustand, FSD.

Подробнее — Технологии и библиотеки.

Ключевые особенности

  • Генерация вместо ручного создания — компоненты, фичи, виджеты, сторы и другие модули не создаются вручную. Файловая структура генерируется из шаблонов .templates/. Ручное создание файловой структуры модулей запрещено.
  • Biome вместо ESLint + Prettier — один инструмент для линтинга и форматирования. Автофикс и сортировка импортов происходят автоматически при сохранении файла.

Настройка окружения

Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — Настройка VS Code.

Создание проекта

Как начать новый проект, соответствующий стандартам этого руководства.

Что нужно знать

Новый проект создаётся из готового шаблона. Шаблон содержит настроенный стек, структуру FSD, конфигурацию редактора и шаблоны генерации кода — проект готов к разработке сразу после установки зависимостей.

Создание из шаблона

npx tiged git@gromlab.ru:templates/nextjs.git my-app
cd my-app
npm install

Что входит в шаблон

  • Next.js + TypeScript (App Router)
  • Mantine UI + PostCSS Modules
  • Biome (линтинг и форматирование)
  • Zustand, SWR
  • Структура FSD (screens/, widgets/, features/, entities/, shared/)
  • Шаблоны генерации (.templates/)
  • Конфигурация VS Code (.vscode/)
  • CSS-токены (цвета, отступы, радиусы, медиа)
  • Open Graph метаданные

Генерация кода

Как создавать модули в проекте с помощью шаблонов — какие модули покрыты генерацией и когда стоит создавать новые шаблоны.

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

Модуль Слой Шаблон
Компонент shared/ui/ component
Фича features/ feature
Виджет widgets/ widget
Сущность entities/ entity
Layout layouts/ layout
Экран screens/ screen
Стор model/ store

Что нужно знать

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

Если для нужного модуля нет подходящего шаблона — стоит сначала создать шаблон, а затем использовать его.

Когда создавать новый шаблон

  • Повторяющаяся структура появляется больше одного раза.
  • Существующий шаблон не покрывает нужный тип модуля.

Инструменты и синтаксис шаблонов — Шаблоны и генерация кода.

Добавление страницы

Как добавить новую страницу в проект по стандартам этого руководства.

Что нужно знать

Страница в проекте — это два файла: экран в src/screens/ (вся логика, стили, зависимости) и page.tsx в src/app/ (точка входа для роутинга Next.js). Экран генерируется из шаблона, page.tsx создаётся вручную.

Порядок действий

  1. Сгенерировать экран из шаблона screen в папку src/screens/.

  2. Заполнить экран логикой и стилями.

  3. Создать page.tsx в нужном маршруте src/app/. Файл страницы должен быть тонким — только metadata и рендер экрана. Никакой логики, стилей и хуков в page.tsx не размещается — всё это живёт в экране.

Правила

  • Ручное создание файловой структуры экрана запрещено — только генерация из шаблона.
  • Логика, стили и зависимости размещаются в экране, не в page.tsx.
  • Каждая страница содержит metadata с title и description.

Примеры page.tsx и metadataPage-level компоненты.

Добавление UI-модуля

Как создать компонент, фичу, виджет, сущность или layout в проекте.

Что нужно знать

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

Порядок действий

  1. Сгенерировать модуль из соответствующего шаблона в целевой слой.
  2. Заполнить модуль логикой и стилями.

Дочерние компоненты

Если модулю нужны внутренние подкомпоненты — генерировать их из шаблона component в папку ui/ внутри родительского модуля. Дочерние компоненты не экспортируются через index.ts родителя.

Правила написания компонентов — Компоненты.

Стилизация

Как стилизовать компоненты в проекте — приоритет инструментов и правила их применения.

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

Основной UI-фреймворк проекта — Mantine. При стилизации компонентов придерживаться следующего приоритета:

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

Что запрещено

  • Инлайн-стили — использование атрибута style в компонентах строго запрещено.
  • Магические значения — произвольные цвета, отступы и скругления запрещены, использовать токены.
  • Глобальные стили вне app/styles/ запрещены.

Правила написания CSS, вложенность, медиа-запросы и токены — Стили.

Получение данных

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

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

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

Локализация

Как добавлять переводы и подключать локализацию через 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 сущности

Остальные .tsx файлы (компоненты в shared/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.tsx
│       ├── auth-by-email.feature.tsx
│       └── index.ts
└── shared/
    └── ui/
        └── icon/
            ├── styles/
            │   └── icon.module.css
            ├── types/
            │   └── icon.interface.ts
            ├── icon.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;

Настройка 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
MyTemplateGenerator Генерация файлов и папок из шаблонов .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": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "files.associations": {
    "*.css": "postcss"
  }
}

Разбор настроек

Настройка Значение Что делает
editor.defaultFormatter biomejs.biome Biome используется как единственный форматтер для всех файлов
editor.formatOnSave true Код автоматически форматируется при каждом сохранении
codeActionsOnSave.quickfix.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 с общими для команды настройками.

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

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

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

src/
├── app/                  # Слой app: роутинг, провайдеры, глобальные стили
│   ├── providers/        # Провайдеры и обёртки приложения
│   ├── styles/           # Глобальные стили, CSS-переменные, custom media
│   ├── layout.tsx        # Корневой layout (провайдеры, стили, метаданные)
│   ├── page.tsx          # Главная страница → HomeScreen
│   └── profile/
│       ├── page.tsx      # → ProfileScreen
│       └── [id]/
│           └── page.tsx  # → ProfileDetailScreen
├── screens/              # UI-компоненты страниц
│   ├── home/
│   │   ├── home.screen.tsx
│   │   └── index.ts
│   └── 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.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.tsx
    │       └── index.ts
    ├── lib/              # Утилиты и хелперы
    ├── services/         # Общие сервисы и клиенты
    ├── config/           # Общие конфигурации и константы
    └── assets/           # Ресурсы
        ├── images/
        ├── icons/
        ├── fonts/
        └── video/

Слой app/

Папка app/ совмещает две роли: инициализация приложения (провайдеры, глобальные стили) и файловый роутинг Next.js (route-сегменты, layout.tsx, page.tsx).

  • providers/ и styles/ -- это инфраструктура приложения, они не являются частью роутинга.
  • Route-сегменты (вложенные папки с page.tsx) -- это роутинг Next.js. Они не содержат логики, только импортируют экраны из screens/.

Компоненты, хуки, стили и утилиты не размещаются внутри route-сегментов -- всё это живёт в соответствующих слоях FSD.

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

  • В слоях 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.
  • Внутренние файлы не импортируются напрямую извне.
  • Не смешивать ответственность разных слоёв в одном модуле.

Пример организации структуры

Плохо -- плоская структура без архитектуры:

src/
├── components/
│   ├── Header.tsx
│   ├── LoginForm.tsx
│   ├── UserCard.tsx
│   └── Button.tsx
├── hooks/
│   ├── useAuth.ts
│   └── useUser.ts
├── api/
│   ├── auth.ts
│   └── user.ts
├── styles/
│   ├── header.module.css
│   └── login.module.css
├── types/
│   └── user.ts
└── utils/
    └── format.ts

Нет слоёв, нет границ ответственности -- Header и Button лежат рядом, хотя это разные уровни абстракции. LoginForm знает про API напрямую. При росте проекта components/ превращается в свалку.

Хорошо -- та же функциональность в FSD:

src/
├── widgets/
│   └── header/
│       ├── header.widget.tsx
│       └── index.ts
├── features/
│   └── auth-by-email/
│       ├── ui/
│       │   └── login-form.tsx
│       ├── model/
│       │   └── auth.store.ts
│       ├── auth-by-email.feature.tsx
│       └── index.ts
├── entities/
│   └── user/
│       ├── ui/
│       │   └── user-card.tsx
│       ├── model/
│       │   └── user.store.ts
│       ├── user.entity.tsx
│       └── index.ts
└── shared/
    ├── ui/
    │   └── button/
    │       ├── button.tsx
    │       └── index.ts
    └── lib/
        └── format.ts

Каждый элемент на своём слое, с публичным API и чёткими границами ответственности.

Компоненты

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

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

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

container/
├── styles/
│   └── container.module.scss
├── types/
│   └── container.interface.ts
├── container.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.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'

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

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

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

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

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

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

Page-level компоненты

Специальные файлы Next.js App Router, которые фреймворк использует по соглашению: layout.tsx, page.tsx, loading.tsx, error.tsx, not-found.tsx, template.tsx.

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

  • Экспорт через export default function — конвенция Next.js.
  • Типизация через PropsWithChildren или явный интерфейс.
  • Каждая страница (page.tsx) должна содержать metadata с title и description.
  • Минимум логики — page-level компоненты делегируют работу экранам, виджетам и фичам.
  • Стили в page-level компонентах не используются — стилизация внутри вызываемых компонентов.

layout.tsx

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

import type { PropsWithChildren } from 'react'
import type { Metadata } from 'next'
import { ColorSchemeScript, MantineProvider } from '@mantine/core'
import '@mantine/core/styles.css'
import './globals.css'

export const metadata: Metadata = {
  title: {
    default: 'App',
    template: '%s | App',
  },
  description: 'Описание приложения',
  metadataBase: new URL('https://example.com'),
  openGraph: {
    type: 'website',
    locale: 'ru_RU',
    siteName: 'App',
    images: [
      {
        url: '/og-image.png',
        width: 1200,
        height: 630,
        alt: 'App',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
  },
}

export default function RootLayout({ children }: PropsWithChildren) {
  return (
    <html lang="ru" suppressHydrationWarning>
      <head>
        <ColorSchemeScript />
      </head>
      <body>
        <MantineProvider>
          {children}
        </MantineProvider>
      </body>
    </html>
  )
}

Вложенный layout — для секции с общей обёрткой (sidebar, header):

import type { PropsWithChildren } from 'react'
import { DashboardLayout } from '@/shared/ui/dashboard-layout'

export default function Layout({ children }: PropsWithChildren) {
  return (
    <DashboardLayout>
      {children}
    </DashboardLayout>
  )
}

page.tsx

Тонкий файл — только импорт и рендер экрана. Логика, стили и зависимости размещаются в экране, не в page.tsx.

import type { Metadata } from 'next'
import { HomeScreen } from '@/screens/home'

export const metadata: Metadata = {
  title: 'Главная',
  description: 'Главная страница приложения',
}

export default function HomePage() {
  return <HomeScreen />
}

С параметрами маршрута:

import type { Metadata } from 'next'
import { ProfileScreen } from '@/screens/profile'

export const metadata: Metadata = {
  title: 'Профиль',
  description: 'Страница профиля пользователя',
}

interface ProfilePageProps {
  params: Promise<{ id: string }>
}

export default async function ProfilePage({ params }: ProfilePageProps) {
  const { id } = await params

  return <ProfileScreen id={id} />
}

Каждая страница должна содержать metadata с title — он подставится в шаблон из корневого layout: Профиль | App.

loading.tsx

Состояние загрузки. Показывается пока загружается контент страницы.

export default function Loading() {
  return <div>Загрузка...</div>
}

error.tsx

Обработка ошибок. Обязательно 'use client' — error boundary работает только на клиенте. Разметку выносим в экран.

'use client'

import type { FC } from 'react'
import { ErrorScreen } from '@/screens/error'

interface ErrorPageProps {
  error: Error & { digest?: string }
  reset: () => void
}

const ErrorPage: FC<ErrorPageProps> = ({ error, reset }) => {
  return <ErrorScreen error={error} reset={reset} />
}

export default ErrorPage

not-found.tsx

Страница 404. Показывается когда маршрут не найден. Разметку выносим в экран.

import type { Metadata } from 'next'
import { NotFoundScreen } from '@/screens/not-found'

export const metadata: Metadata = {
  title: 'Страница не найдена',
  description: 'Запрашиваемая страница не существует',
}

export default function NotFound() {
  return <NotFoundScreen />
}

template.tsx

Аналог layout, но пересоздаётся при каждой навигации (не сохраняет состояние). Используется редко — для анимаций переходов между страницами.

import type { PropsWithChildren } from 'react'

export default function Template({ children }: PropsWithChildren) {
  return <div>{children}</div>
}

::: v-pre

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

Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI.

Структура шаблонов

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

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

Как создать новый шаблон

  1. Создать папку в .templates/ с именем шаблона (например hook).
  2. Внутри разместить файлы и папки, используя {{name}} и модификаторы в именах и содержимом.
  3. Шаблон сразу доступен и в расширении 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}}.interface.ts
import type { HTMLAttributes } from 'react'

/**
 * Параметры {{name.pascalCase}}.
 */
export interface {{name.pascalCase}}Props extends HTMLAttributes<HTMLDivElement> {}
// .templates/component/{{name.kebabCase}}.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 {

}

Генерация через VS Code

MyTemplateGenerator — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.

  1. ПКМ на целевой папке в проводнике VS Code.
  2. Generate from template → выбрать шаблон.
  3. Ввести имя (например button) — расширение подставит его во все переменные {{name}}.

Генерация через CLI

@gromlab/create — CLI для генерации из тех же шаблонов. Используется через npx, глобальная установка не требуется.

npx @gromlab/create <шаблон> <имя> <путь>
Команда Что создаёт
npx @gromlab/create component button src/shared/ui Компонент
npx @gromlab/create feature auth src/features Фичу
npx @gromlab/create widget header src/widgets Виджет
npx @gromlab/create entity user src/entities Сущность
npx @gromlab/create layout admin src/layouts Layout
npx @gromlab/create store auth src/shared/model Стор

:::

Стили

Раздел описывает правила написания 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-свойств

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

  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-спрайты