# 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 | _(не заполнен)_ | | Хуки | _(не заполнен)_ | | Шрифты | _(не заполнен)_ | | Локализация | _(не заполнен)_ | ## Workflow Порядок действий при разработке — от создания проекта до реализации фич. ### Начало работы Подготовка окружения перед началом разработки. 1. Открыть проект в VS Code. 2. Установить рекомендуемые расширения (редактор предложит автоматически). 3. Ознакомиться со стеком: Next.js (App Router), Mantine, Zustand, FSD. ### Создание проекта Инициализация нового проекта из готового шаблона. 1. Создать проект из шаблона: ```bash 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-компоненты. #### Fetch (API) - **@gromlab/api-codegen** — генерация API‑клиентов и типов. - **SWR** — получение, кеширование, ревалидация, дедубликация. - **SWR (useSWRSubscription)** - сокеты, реалтайм подписки. #### Store - **Zustand** — глобальное состояние. #### Локализация - **i18next (i18n)** — локализация всех пользовательских текстов. #### Тестирование - **Vitest** — тестирование. #### Стили - **PostCSS Modules** — изоляция стилей. - **Mobile First** — подход к адаптивной верстке. - **clsx** — конкатенация CSS‑классов. #### Генерация - **@gromlab/create** — шаблонизатор для создания слоёв и других файлов из шаблонов. ## Архитектура Архитектура построена на FSD (`Feature‑Sliced 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 символов, но превышение допустимо, если строка читается легко. - Переносить выражение на новые строки, когда строка становится плохо читаемой. - Не переносить строку внутри строковых литералов без необходимости. **Хорошо** ```ts const config = createRequestConfig( endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId, }, params: { page, pageSize, sort: 'createdAt', }, }, timeoutMs, ); ``` **Плохо** ```ts // Плохо: длинная строка с вложенными структурами плохо читается. const config = createRequestConfig(endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId }, params: { page, pageSize, sort: 'createdAt' } }, timeoutMs); ``` ### Кавычки - В JavaScript/TypeScript использовать одинарные кавычки. - В JSX/TSX для атрибутов использовать двойные кавычки. - Шаблонные строки использовать только при интерполяции или многострочном тексте. **Хорошо** ```ts const label = 'Сохранить'; const title = `Привет, ${name}`; ``` ```tsx ``` **Плохо** ```ts // Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки. const label = "Сохранить"; const title = 'Привет, ' + name; ``` ```tsx // Плохо: одинарные кавычки в JSX-атрибутах. ``` ### Точки с запятой и запятые - Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным. - В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна. ### Импорты - В именованных импортах использовать пробелы внутри фигурных скобок. - Типы импортировать через `import type`. - `default` экспорт избегать, использовать именованные. `default` импорт допустим (например, стили CSS Modules, сторонние библиотеки). - Избегать импорта всего модуля через `*`. **Хорошо** ```ts import { MyComponent } from 'MyComponent'; import type { User } from '../model/types'; import styles from './styles/button.module.css'; ``` **Плохо** ```ts // Плохо: отсутствие пробелов в именованном импорте. import type {User} from '../model/types'; // Плохо: default экспорт. export default MyComponent; ``` ### Ранние возвраты (early return) - Использовать ранние возвраты для упрощения чтения. - Избегать `else` после `return`. **Хорошо** ```ts const getName = (user?: { name: string }) => { if (!user) { return 'Гость'; } return user.name; }; ``` **Плохо** ```ts // Плохо: лишний else после return усложняет чтение. const getName = (user?: { name: string }) => { if (user) { return user.name; } else { return 'Гость'; } }; ``` ### Форматирование объектов и массивов - В многострочных объектах каждое свойство на новой строке. - В многострочных массивах каждый элемент на новой строке. - Объекты и массивы можно писать в одну строку, если длина строки не превышает 100 символов. - В однострочных объектах и массивах использовать пробелы после запятых. **Хорошо** ```ts const roles = ['admin', 'editor', 'viewer']; const options = { id: 1, name: 'User' }; const config = { url: '/api/users', method: 'GET', params: { page: 1, pageSize: 20 }, }; ``` **Плохо** ```ts // Плохо: нет пробелов после запятых и объект слишком длинный для одной строки. 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..ts` или `name..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` **Хорошо** ```text 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 ``` **Плохо** ```text // Плохо: нет единых правил для слоёв и публичных файлов. src/ ├── screens/ │ └── Main/ │ └── Main.tsx └── features/ └── authByEmail/ └── login-form.tsx ``` ### Булевы значения - Использовать префиксы `is`, `has`, `can`, `should`. **Хорошо** ```ts const isReady = true; const hasAccess = false; const canSubmit = true; const shouldRedirect = false; ``` **Плохо** ```ts // Плохо: неясное булево значение без префикса. const ready = true; const access = false; const submit = true; ``` ### События и обработчики - Обработчики начинать с `handle`. - События и колбэки начинать с `on`. **Хорошо** ```ts const handleSubmit = () => { ... }; const onSubmit = () => { ... }; ``` **Плохо** ```ts // Плохо: неочевидное назначение имени. const submitClick = () => { ... }; ``` ### Коллекции - Для массивов использовать имена во множественном числе. - Для словарей/мап — использовать суффиксы `ById`, `Map`, `Dict`. **Хорошо** ```ts const users = []; const usersById = {} as Record; const userIds = ['u1', 'u2']; const ordersMap = new Map(); const featureFlagsDict = { beta: true, legacy: false } as Record; ``` **Плохо** ```ts // Плохо: имя не отражает, что это коллекция. const user = []; // Плохо: словарь назван как массив. const usersMap = []; // Плохо: по имени непонятно, что это словарь. const users = {} as Record; ``` ## Документирование Документирование должно помогать понять назначение сущности, а не дублировать её типы или очевидные детали. ### Правила - Документировать только назначение функций, компонентов, типов, интерфейсов и enum. - Не документировать параметры, возвращаемые значения, типы пропсов и очевидные детали. - В интерфейсах, типах и enum описывать только смысл поля или значения. - Описание должно быть кратким, информативным и завершаться точкой. ### Примеры **Хорошо** ```ts /** * Список задач пользователя. */ export const TodoList = memo(() => { ... }); /** * Интерфейс задачи. */ export interface TodoItem { /** Уникальный идентификатор задачи. */ id: string; /** Текст задачи. */ text: string; /** Статус выполнения задачи. */ completed: boolean; } /** * Перечисление фильтров задач. */ export enum TodoFilter { /** Все задачи. */ All = 'all', /** Только активные задачи. */ Active = 'active', /** Только выполненные задачи. */ Completed = 'completed', } ``` **Плохо** ```ts // Плохо: дублирование параметров и возвращаемых значений. /** * @param id - идентификатор задачи * @returns объект задачи */ // Плохо: описание очевидных деталей. /** * id — идентификатор задачи * text — текст задачи * completed — статус выполнения */ ``` ## Типизация Типизация обязательна для всех публичных интерфейсов, функций и компонентов. Цель — предсказуемость, безопасность и автодополнение. ### Общие правила - Указывать типы для параметров компонентов, возвращаемых значений и параметров функций. - Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов. - Избегать `any` и `unknown` без необходимости. - Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины. ### Типы для компонентов - Типизировать параметры и публичный интерфейс компонента. - Дефолтные значения описывать явно в коде. **Хорошо** ```tsx /** * Параметры кнопки. */ interface ButtonProps extends HTMLAttributes { /** Текст кнопки. */ label: string; /** Обработчик клика по кнопке. */ onClick: () => void; } /** * Кнопка с пользовательскими стилями. */ export const Button: FC = ({ className, label, onClick, ...htmlAttr }) => { return (
button
) } ``` **Плохо** ```tsx // Плохо: параметры не типизированы. export const Button = (props) => ( ); ``` ### Функции - Для публичных функций указывать возвращаемый тип. - Не полагаться на неявный вывод для важных API. **Хорошо** ```ts export const formatPrice = (value: number): string => { return `${value} ₽`; }; ``` **Плохо** ```ts // Плохо: нет явного возвращаемого типа. export const formatPrice = (value: number) => { return `${value} ₽`; }; ``` ### Работа с any/unknown - `any` использовать только для временных заглушек. - `unknown` сужать через проверки перед использованием. **Хорошо** ```ts const parse = (value: unknown): string => { if (typeof value === 'string') { return value; } return ''; }; ``` **Плохо** ```ts // Плохо: any отключает проверку типов. const parse = (value: any) => value; ``` ## Настройка VS Code Каждый проект содержит папку `.vscode/` с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями. ### Структура `.vscode/` ```text .vscode/ ├── extensions.json # Рекомендуемые расширения └── settings.json # Настройки редактора для проекта ``` Оба файла коммитятся в репозиторий. ### Расширения Файл `.vscode/extensions.json` определяет список расширений, которые VS Code предложит установить при открытии проекта. ```json // .vscode/extensions.json { "recommendations": [ "biomejs.biome", "MyTemplateGenerator.mytemplategenerator", "csstools.postcss" ] } ``` | Расширение | Назначение | |---|---| | [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier | | [MyTemplateGenerator](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню | | [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) | #### Зачем это нужно - Новый участник команды получает все нужные расширения одним кликом. - Нет разночтений: все используют одинаковый форматтер и линтер. - Расширения привязаны к проекту, а не к конкретному разработчику. ### Настройки редактора Файл `.vscode/settings.json` переопределяет пользовательские настройки VS Code на уровне проекта. ```json // .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` с общими для команды настройками. ## Структура проекта Раздел описывает базовую структуру проекта Next.js (App Router) и принципы организации модулей на уровне папок и файлов. ### Базовая структура проекта ```text 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`. - Внутренние файлы не импортируются напрямую извне. - Не смешивать ответственность разных слоёв в одном модуле. ### Пример организации структуры **Плохо** -- плоская структура без архитектуры: ```text 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: ```text 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`. ### Базовая структура компонента Минимальный набор файлов: компонент, стили, типы и публичный экспорт. ```text container/ ├── styles/ │ └── container.module.css ├── types/ │ └── container.interface.ts ├── container.tsx └── index.ts ``` ### Пример базового компонента `styles/container.module.css` ```scss .root {} ``` В CSS Modules использование имени класса **.root** — это общепринятое соглашение (best practice) `types/container.interface.ts` ```ts import type { HTMLAttributes } from 'react' /** * Параметры контейнера. */ export interface ContainerProps extends HTMLAttributes {} ``` Интерфес параметров компонента всегда наследует свойства своего тега: div, button, итд.. `container.tsx` ```tsx import type { FC } from 'react' import cl from 'clsx' import type { ContainerProps } from './types/container.interface' import styles from './styles/container.module.css' /** * Контейнер с адаптивной максимальной шириной. * * Используется для: * - ограничения ширины контента * - центрирования содержимого * - построения адаптивной сетки страницы */ export const Container: FC = ({ className, ...htmlAttr }) => { return (
Container...
) } ``` - Компонент объявляется через `const` и экспортируется именованно. - Пропсы деструктурируются в сигнатуре; если их больше двух — деструктуризацию переносим в тело компонента. - Из пропсов отдельно извлекаются `className` и `...htmlAttr`, чтобы корректно объединять классы и прокидывать остальные атрибуты. - `cl` — короткое имя функции для конкатенации CSS‑классов. - `FC<>` используется для декларации `children`. `index.ts` ```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 — точка подключения провайдеров, глобальных стилей и метаданных. ```tsx import type { PropsWithChildren } from 'react' import type { Metadata } from 'next' import { Providers } from './providers' import './styles/index.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 ( {children} ) } ``` Вложенный layout — для секции с общей обёрткой (sidebar, header): ```tsx import type { PropsWithChildren } from 'react' import { DashboardLayout } from '@/shared/ui/dashboard-layout' export default function Layout({ children }: PropsWithChildren) { return ( {children} ) } ``` ### page.tsx Тонкий файл — только импорт и рендер экрана. Логика, стили и зависимости размещаются в экране, не в `page.tsx`. ```tsx import type { Metadata } from 'next' import { HomeScreen } from '@/screens/home' export const metadata: Metadata = { title: 'Главная', description: 'Главная страница приложения', } export default function HomePage() { return } ``` С параметрами маршрута: ```tsx 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 } ``` Каждая страница должна содержать `metadata` с `title` — он подставится в шаблон из корневого layout: `Профиль | App`. ### loading.tsx Состояние загрузки. Показывается пока загружается контент страницы. ```tsx export default function Loading() { return
Загрузка...
} ``` ### error.tsx Обработка ошибок. Обязательно `'use client'` — error boundary работает только на клиенте. Разметку выносим в экран. ```tsx 'use client' import type { FC } from 'react' import { ErrorScreen } from '@/screens/error' interface ErrorPageProps { error: Error & { digest?: string } reset: () => void } const ErrorPage: FC = ({ error, reset }) => { return } export default ErrorPage ``` ### not-found.tsx Страница 404. Показывается когда маршрут не найден. Разметку выносим в экран. ```tsx import type { Metadata } from 'next' import { NotFoundScreen } from '@/screens/not-found' export const metadata: Metadata = { title: 'Страница не найдена', description: 'Запрашиваемая страница не существует', } export default function NotFound() { return } ``` ### template.tsx Аналог layout, но пересоздаётся при каждой навигации (не сохраняет состояние). Используется редко — для анимаций переходов между страницами. ```tsx import type { PropsWithChildren } from 'react' export default function Template({ children }: PropsWithChildren) { return
{children}
} ``` ::: v-pre ## Шаблоны и генерация кода Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI. ### Структура шаблонов Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон. ```text .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`. ```text {{variable}} ``` Модификаторы меняют регистр и формат записи: ```text {{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. Пример — создание шаблона для хука: ```text .templates/ └── hook/ └── {{name.kebabCase}}/ ├── {{name.kebabCase}}.hook.ts └── index.ts ``` ```ts // .templates/hook/{{name.kebabCase}}.hook.ts export const {{name.camelCase}} = () => { } ``` ```ts // .templates/hook/index.ts export { {{name.camelCase}} } from './{{name.kebabCase}}.hook' ``` ### Примеры шаблонов #### Шаблон компонента ```ts // .templates/component/index.ts export { {{name.pascalCase}} } from './{{name.kebabCase}}' ``` ```ts // .templates/component/types/{{name.kebabCase}}.interface.ts import type { HTMLAttributes } from 'react' /** * Параметры {{name.pascalCase}}. */ export interface {{name.pascalCase}}Props extends HTMLAttributes {} ``` ```tsx // .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 (
{{name.kebabCase}}
) } ``` ```css /* .templates/component/styles/{{name.kebabCase}}.module.css */ .root { } ``` ### Генерация через VS Code [MyTemplateGenerator](https://open-vsx.org/extension/MyTemplateGenerator/mytemplategenerator) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора. 1. ПКМ на целевой папке в проводнике VS Code. 2. **Generate from template** → выбрать шаблон. 3. Ввести имя (например `button`) — расширение подставит его во все переменные `{{name}}`. ### Генерация через CLI [@gromlab/create](https://www.npmjs.com/package/@gromlab/create) — CLI для генерации из тех же шаблонов. Используется через npx, глобальная установка не требуется. ```bash 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`. **Хорошо** ```css .submitButton { padding: 8px 16px; &._disabled { opacity: 0.5; } } ``` **Плохо** ```css /* Плохо: kebab-case и вложенный элемент вместо отдельного класса. */ .submit-button { padding: 8px 16px; &__icon { margin-right: 8px; } } ``` ### Вложенность - Вложенность селекторов запрещена. - Исключения: - Псевдоклассы: `&:hover`, `&:active`, `&:focus`, `&:disabled` и т.д. - Псевдоэлементы: `&::before`, `&::after`. - Медиа-запросы: `@media`. - Модификаторы: `&._active`, `&._disabled`. - Каждый вложенный блок отделяется пустой строкой от предыдущих свойств. **Хорошо** ```css .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; } } ``` **Плохо** ```css /* Плохо: вложенность селекторов, нет пустых строк между блоками. */ .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` на верхнем уровне с селекторами внутри. **Хорошо** ```css .sidebar { display: none; @media (--md) { display: block; } } .sidebarTitle { font-size: 14px; @media (--md) { font-size: 18px; } } ``` **Плохо** ```css /* Плохо: @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 — после этого переменные доступны глобально через каскад. - Не дублировать магические значения в компонентах. **Хорошо** ```css /* 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; } ``` ```css /* компонент */ .card { padding: var(--space-3); border-radius: var(--radius-2); background-color: var(--color-bg); } ``` **Плохо** ```css /* Плохо: магические значения вместо переменных. */ .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`) — не импортировать в файлы стилей. ```css /* 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. ### Форматирование - Пустая строка между селекторами верхнего уровня. - Пустая строка перед каждым вложенным блоком (медиа, псевдокласс, модификатор). **Хорошо** ```css .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); } } ``` **Плохо** ```css /* Плохо: нет пустых строк между селекторами и вложенными блоками. */ .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-спрайты