Files
nextjs-style-guide/generated/ru/RULES.md
S.Gromov fbac3e1a55
All checks were successful
CI/CD Pipeline / docker (push) Successful in 1m41s
CI/CD Pipeline / deploy (push) Successful in 8s
docs: отказаться от FC и interface в пропсах компонентов
- переписана типизация: type вместо interface, убран FC
- введена система типов: Params + RootAttrs + Props
- добавлены обоснования: почему type, почему не FC, почему types/
- обновлены примеры в components, page-level, templates, documentation
- убраны упоминания FC из таблиц index.md и README
- перегенерированы RULES.md
2026-04-02 16:01:29 +03:00

56 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?

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

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

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

Workflow

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

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

Инициализация нового проекта из готового шаблона.

  1. Создать проект из шаблона:
    npx tiged git@gromlab.ru:templates/nextjs.git my-app
    cd my-app
    npm install
    
  2. Проект готов к разработке — стек, структура FSD, конфигурация редактора и шаблоны генерации уже настроены.

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

Создание модулей из шаблонов .templates/ вместо ручного создания файлов.

  1. Определить тип модуля и соответствующий шаблон:

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

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

Ручное создание файловой структуры модулей запрещено.

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

Создание нового маршрута: экран + точка входа для роутинга.

  1. Сгенерировать экран из шаблона screen в src/screens/.
  2. Заполнить экран логикой и стилями.
  3. Создать page.tsx в нужном маршруте src/app/.

page.tsx — тонкая обёртка: только metadata и рендер экрана. Логика, стили и хуки размещаются в экране, не в page.tsx.

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

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

  1. Сгенерировать модуль из соответствующего шаблона в целевой слой.
  2. Заполнить модуль логикой и стилями.
  3. Дочерние компоненты — генерировать из шаблона component в папку ui/ внутри родителя.

Дочерние компоненты не экспортируются через index.ts родителя.

Стилизация

Выбор инструмента стилизации по приоритету.

  1. Использовать Mantine-компоненты и их пропсы.
  2. Если Mantine не покрывает — использовать CSS-токены (--color-*, --space-*, --radius-*).
  3. Если нужна кастомная стилизация — PostCSS Modules.

Инлайн-стили (style), магические значения и глобальные стили вне app/styles/ запрещены.

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

Раздел в разработке — SWR, генерация API-клиентов, сокеты.

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

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

Локализация

Раздел в разработке — переводы и i18next.

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

Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.

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

Стек

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

Архитектура

  • FSD (Feature-Sliced 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 — моки

Хорошо

features/
└── auth-by-email/
    ├── ui/
    │   └── login-form.tsx
    ├── hooks/
    │   └── use-auth.hook.ts
    ├── stores/
    │   └── auth.store.ts
    ├── types/
    │   └── auth.interface.ts
    ├── auth-by-email.feature.tsx
    └── index.ts

Плохо

features/
└── 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>;

Архитектура

Этот раздел описывает архитектуру проекта: из каких слоёв состоит приложение, как организован код внутри слоёв и какие правила управляют зависимостями.

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

Проект использует FSD (Feature-Sliced Design) как базовую архитектурную методологию. Если вы не знакомы с FSD — начните с официальной документации.

Данная архитектура является надстройкой над FSD, а не заменой. Все правила FSD действуют по умолчанию — если правило явно не переопределено в этом документе, применяется стандарт FSD. Единственное отклонение: вместо слайсов используются компоненты.

Слои

Слой Назначение
app Инициализация: провайдеры, стили, роутинг Next.js
screens Сборка страницы из виджетов и фич
layouts Каркасы и шаблоны страниц
widgets Крупные блоки интерфейса
features Пользовательские сценарии и действия
entities Бизнес-сущности
shared Утилиты, UI-кит, инфраструктура

Слой pages не используется — конфликтует с Next.js. Вместо него: screens и layouts.

Зависимости идут строго сверху вниз: app → screens → layouts → widgets → features → entities → shared.

Компоненты

Компонент — стандартная UI-единица, такая же как в любом React-проекте. Содержит корневой .tsx, публичный API (index.ts) и сегменты.

Компоненты располагаются в:

  • shared/ui/ — переиспользуемые компоненты без бизнес-контекста
  • ui/ внутри master component'а — дочерние компоненты (подробнее в разделе Master component)

Сегменты

Сегмент — папка внутри компонента, группирующая код по техническому назначению. Набор не фиксирован.

Сегмент Назначение
styles/ Стили
types/ Интерфейсы, типы, enums, DTO
ui/ Компоненты, провайдеры и любые другие элементы интерфейса
stores/ Сторы состояния
hooks/ React-хуки
services/ Внешние источники данных
lib/ Утилиты
helpers/ Вспомогательные функции
config/ Константы, конфигурация

Master component

Master component — это обычный компонент, на который наложен ряд дополнительных правил. Эти правила определяют его место в архитектуре и границы зависимостей.

  • Может располагаться только в слоях: screens, layouts, widgets, features, entities
  • Импортирует master component'ы только из слоёв ниже по иерархии
  • Корневой .tsx именуется с суффиксом слоя: header.widget.tsx, auth.feature.tsx
  • Корневой .tsx необязателен — index.ts может экспортировать несколько сущностей напрямую
  • Дочерние компоненты в ui/ доступны снаружи только через index.ts
  • Компоненты внутри одного ui/ могут импортировать друг друга

Стиль кода

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

Отступы

  • 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, провайдеры, глобальные стили
├── screens/    # Собраные страницы (UI)
├── layouts/    # Шаблоны
├── widgets/    # Крупные самостоятельные блоки интерфейса
├── features/   # Пользовательские сценарии
├── entities/   # Бизнес-сущности
└── shared/     # Переиспользуемый код (UI, утилиты, типы и др.)

Принципы организации слоёв описаны в разделе Архитектура.

Папка app/

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

src/app/
├── providers/    # Провайдеры приложения
├── styles/       # Глобальные стили
├── layout.tsx    # Корневой layout
└── page.tsx      # Главная страница

Папка .templates/

Содержит шаблоны для генерации кода. Каждый подкаталог — шаблон отдельного типа модуля:

.templates/
├── component/    # Шаблон компонента
├── screen/       # Шаблон экрана
├── layout/       # Шаблон layout
├── widget/       # Шаблон виджета
├── feature/      # Шаблон фичи
├── entity/       # Шаблон сущности
└── 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.

Архитектурные слои и их назначение описаны в разделе Архитектура.

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

  1. Один компонент — один файл.
  2. Компонент не содержит бизнес-логики — логика и сайд-эффекты выносятся в хуки или сторы.
  3. Дочерние компоненты размещаются в сегменте ui/ и подчиняются тем же правилам структуры.
  4. Публичный 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

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

  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}}.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

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

Настройка 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": {
    "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 с общими для команды настройками.