- Заменён .scss на .css в разделе Компоненты - Заменён globals.css на ./styles/index.css в layout.tsx - Провайдеры вынесены в app/providers/ вместо инлайн-импорта - Обновлён quickfix.biome на source.fixAll.biome (Biome 2.x) - Breakpoints и радиусы оставлены без изменений (36/62/82em, --radius-1/2)
1695 lines
67 KiB
Markdown
1695 lines
67 KiB
Markdown
<!-- /index -->
|
||
# 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/getting-started -->
|
||
## Начало работы
|
||
|
||
Что нужно знать перед началом разработки в проекте.
|
||
|
||
### Стек проекта
|
||
|
||
**Next.js** (App Router), **Mantine**, **Zustand**, **FSD**.
|
||
|
||
Подробнее — [Технологии и библиотеки](/basics/tech-stack).
|
||
|
||
### Ключевые особенности
|
||
|
||
- **Генерация вместо ручного создания** — компоненты, фичи, виджеты, сторы и другие модули не создаются вручную. Файловая структура генерируется из шаблонов `.templates/`. Ручное создание файловой структуры модулей запрещено.
|
||
- **Biome вместо ESLint + Prettier** — один инструмент для линтинга и форматирования. Автофикс и сортировка импортов происходят автоматически при сохранении файла.
|
||
|
||
### Настройка окружения
|
||
|
||
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/applied/vscode).
|
||
|
||
<!-- /workflow/creating-app -->
|
||
## Создание проекта
|
||
|
||
Как начать новый проект, соответствующий стандартам этого руководства.
|
||
|
||
### Что нужно знать
|
||
|
||
Новый проект создаётся из готового шаблона. Шаблон содержит настроенный стек, структуру FSD, конфигурацию редактора и шаблоны генерации кода — проект готов к разработке сразу после установки зависимостей.
|
||
|
||
#### Создание из шаблона
|
||
|
||
```bash
|
||
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 метаданные
|
||
|
||
<!-- /workflow/code-generation -->
|
||
## Генерация кода
|
||
|
||
Как создавать модули в проекте с помощью шаблонов — какие модули покрыты генерацией и когда стоит создавать новые шаблоны.
|
||
|
||
### Какие модули генерируются из шаблонов
|
||
|
||
| Модуль | Слой | Шаблон |
|
||
|---|---|---|
|
||
| Компонент | `shared/ui/` | `component` |
|
||
| Фича | `features/` | `feature` |
|
||
| Виджет | `widgets/` | `widget` |
|
||
| Сущность | `entities/` | `entity` |
|
||
| Layout | `layouts/` | `layout` |
|
||
| Экран | `screens/` | `screen` |
|
||
| Стор | `model/` | `store` |
|
||
|
||
### Что нужно знать
|
||
|
||
В проекте принято создавать модули из шаблонов `.templates/`. Шаблоны задают единообразную файловую структуру и сокращают рутину — не нужно вручную создавать папки, файлы типов, стилей и экспорты.
|
||
|
||
Если для нужного модуля нет подходящего шаблона — стоит сначала создать шаблон, а затем использовать его.
|
||
|
||
### Когда создавать новый шаблон
|
||
|
||
- Повторяющаяся структура появляется больше одного раза.
|
||
- Существующий шаблон не покрывает нужный тип модуля.
|
||
|
||
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/applied/templates-generation).
|
||
|
||
<!-- /workflow/creating-pages -->
|
||
## Добавление страницы
|
||
|
||
Как добавить новую страницу в проект по стандартам этого руководства.
|
||
|
||
### Что нужно знать
|
||
|
||
Страница в проекте — это два файла: экран в `src/screens/` (вся логика, стили, зависимости) и `page.tsx` в `src/app/` (точка входа для роутинга Next.js). Экран генерируется из шаблона, `page.tsx` создаётся вручную.
|
||
|
||
### Порядок действий
|
||
|
||
1. [Сгенерировать](/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
|
||
|
||
2. Заполнить экран логикой и стилями.
|
||
|
||
3. Создать `page.tsx` в нужном маршруте `src/app/`. Файл страницы должен быть тонким — только `metadata` и рендер экрана. Никакой логики, стилей и хуков в `page.tsx` не размещается — всё это живёт в экране.
|
||
|
||
### Правила
|
||
|
||
- Ручное создание файловой структуры экрана запрещено — только [генерация](/applied/templates-generation) из шаблона.
|
||
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
|
||
- Каждая страница содержит `metadata` с `title` и `description`.
|
||
|
||
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/applied/page-level).
|
||
|
||
<!-- /workflow/creating-components -->
|
||
## Добавление UI-модуля
|
||
|
||
Как создать компонент, фичу, виджет, сущность или layout в проекте.
|
||
|
||
### Что нужно знать
|
||
|
||
Все UI-модули создаются только из шаблонов `.templates/`. Ручное создание файловой структуры запрещено. Если подходящего шаблона нет — сначала создать шаблон в `.templates/`, затем использовать его.
|
||
|
||
### Порядок действий
|
||
|
||
1. [Сгенерировать](/applied/templates-generation) модуль из соответствующего шаблона в целевой слой.
|
||
2. Заполнить модуль логикой и стилями.
|
||
|
||
### Дочерние компоненты
|
||
|
||
Если модулю нужны внутренние подкомпоненты — [генерировать](/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
|
||
|
||
Правила написания компонентов — [Компоненты](/applied/components).
|
||
|
||
<!-- /workflow/styling -->
|
||
## Стилизация
|
||
|
||
Как стилизовать компоненты в проекте — приоритет инструментов и правила их применения.
|
||
|
||
### Приоритет стилизации
|
||
|
||
Основной UI-фреймворк проекта — **Mantine**. При стилизации компонентов придерживаться следующего приоритета:
|
||
|
||
1. **Mantine-компоненты и их пропсы** — в первую очередь использовать встроенные возможности Mantine (пропсы, `classNames`, `styles`).
|
||
2. **Глобальные CSS-токены** (`--color-*`, `--space-*`, `--radius-*`) — для значений, которые не покрываются Mantine.
|
||
3. **PostCSS Modules** — когда Mantine не покрывает задачу и нужна кастомная стилизация.
|
||
|
||
### Что запрещено
|
||
|
||
- **Инлайн-стили** — использование атрибута `style` в компонентах строго запрещено.
|
||
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
|
||
- **Глобальные стили** вне `app/styles/` запрещены.
|
||
|
||
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили](/applied/styles).
|
||
|
||
<!-- /workflow/data-fetching -->
|
||
## Получение данных
|
||
|
||
Как получать данные с сервера — SWR, генерация API-клиентов, сокеты.
|
||
|
||
<!-- /workflow/state-management -->
|
||
## Управление состоянием
|
||
|
||
Как работать с состоянием — когда создавать стор, что хранить локально и глобально.
|
||
|
||
<!-- /workflow/localization -->
|
||
## Локализация
|
||
|
||
Как добавлять переводы и подключать локализацию через i18next.
|
||
|
||
<!-- /basics/tech-stack -->
|
||
## Технологии и библиотеки
|
||
|
||
Базовый стек технологий и библиотек, на который опираются проекты и примеры в документации.
|
||
|
||
### Что используем
|
||
|
||
#### Стек
|
||
- **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** — шаблонизатор для создания слоёв и других файлов из шаблонов.
|
||
|
||
<!-- /basics/architecture -->
|
||
## Архитектура
|
||
|
||
Архитектура построена на 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.
|
||
|
||
<!-- /basics/code-style -->
|
||
## Стиль кода
|
||
|
||
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
|
||
|
||
### Отступы
|
||
|
||
- 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
|
||
<input type="text" placeholder="Введите имя" />
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки.
|
||
const label = "Сохранить";
|
||
const title = 'Привет, ' + name;
|
||
```
|
||
|
||
```tsx
|
||
// Плохо: одинарные кавычки в JSX-атрибутах.
|
||
<input type='text' placeholder='Введите имя' />
|
||
```
|
||
|
||
### Точки с запятой и запятые
|
||
|
||
- Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным.
|
||
- В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна.
|
||
|
||
### Импорты
|
||
|
||
- В именованных импортах использовать пробелы внутри фигурных скобок.
|
||
- Типы импортировать через `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 } };
|
||
```
|
||
|
||
<!-- /basics/naming -->
|
||
## Именование
|
||
|
||
Именование должно быть предсказуемым, коротким и отражать смысл сущности.
|
||
|
||
### Базовые правила
|
||
|
||
| Что | Рекомендуется |
|
||
| ---------------- | ---------------------- |
|
||
| Папки | `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`
|
||
|
||
|
||
**Хорошо**
|
||
```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<string, User>;
|
||
const userIds = ['u1', 'u2'];
|
||
const ordersMap = new Map<string, Order>();
|
||
const featureFlagsDict = { beta: true, legacy: false } as Record<string, boolean>;
|
||
```
|
||
|
||
**Плохо**
|
||
```ts
|
||
// Плохо: имя не отражает, что это коллекция.
|
||
const user = [];
|
||
// Плохо: словарь назван как массив.
|
||
const usersMap = [];
|
||
// Плохо: по имени непонятно, что это словарь.
|
||
const users = {} as Record<string, User>;
|
||
```
|
||
|
||
<!-- /basics/documentation -->
|
||
## Документирование
|
||
|
||
Документирование должно помогать понять назначение сущности, а не дублировать её типы или очевидные детали.
|
||
|
||
### Правила
|
||
|
||
- Документировать только назначение функций, компонентов, типов, интерфейсов и 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 — статус выполнения
|
||
*/
|
||
```
|
||
|
||
<!-- /basics/typing -->
|
||
## Типизация
|
||
|
||
Типизация обязательна для всех публичных интерфейсов, функций и компонентов.
|
||
Цель — предсказуемость, безопасность и автодополнение.
|
||
|
||
### Общие правила
|
||
|
||
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
|
||
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
||
- Избегать `any` и `unknown` без необходимости.
|
||
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
||
|
||
### Типы для компонентов
|
||
|
||
- Типизировать параметры и публичный интерфейс компонента.
|
||
- Дефолтные значения описывать явно в коде.
|
||
|
||
**Хорошо**
|
||
```tsx
|
||
/**
|
||
* Параметры кнопки.
|
||
*/
|
||
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>
|
||
)
|
||
}
|
||
```
|
||
|
||
**Плохо**
|
||
```tsx
|
||
// Плохо: параметры не типизированы.
|
||
export const Button = (props) => (
|
||
<button type="button" onClick={props.onClick}>
|
||
{props.label}
|
||
</button>
|
||
);
|
||
```
|
||
|
||
### Функции
|
||
|
||
- Для публичных функций указывать возвращаемый тип.
|
||
- Не полагаться на неявный вывод для важных 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;
|
||
```
|
||
|
||
<!-- /applied/vscode -->
|
||
## Настройка 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` с общими для команды настройками.
|
||
|
||
<!-- /applied/project-structure -->
|
||
## Структура проекта
|
||
|
||
Раздел описывает базовую структуру проекта 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 и чёткими границами ответственности.
|
||
|
||
<!-- /applied/components -->
|
||
## Компоненты
|
||
|
||
Раздел описывает правила создания 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<HTMLDivElement> {}
|
||
```
|
||
Интерфес параметров компонента всегда наследует свойства своего тега: 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<ContainerProps> = ({ className, ...htmlAttr }) => {
|
||
return (
|
||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||
Container...
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
- Компонент объявляется через `const` и экспортируется именованно.
|
||
- Пропсы деструктурируются в сигнатуре; если их больше двух — деструктуризацию переносим в тело компонента.
|
||
- Из пропсов отдельно извлекаются `className` и `...htmlAttr`, чтобы корректно объединять классы и прокидывать остальные атрибуты.
|
||
- `cl` — короткое имя функции для конкатенации CSS‑классов.
|
||
- `FC<>` используется для декларации `children`.
|
||
|
||
`index.ts`
|
||
|
||
```ts
|
||
export { Container } from './container'
|
||
```
|
||
|
||
### Шаблоны и генерация кода
|
||
|
||
Создание компонентов — **только через шаблоны**. Ручное создание файловой структуры компонента запрещено. Это обеспечивает единообразие каркаса, одинаковые папки и имена файлов, уменьшает ручные ошибки и ускоряет старт работы.
|
||
|
||
После генерации через **@gromlab/create** — проверить название компонента/файлов и заполнить описание назначения. Подробный порядок действий и перечень обязательных шаблонов — в разделе «Workflow».
|
||
|
||
### Вложенные (дочерние) компоненты
|
||
|
||
Если для реализации функционала нужны компоненты, которые используются только внутри текущего компонента, создавайте их как вложенные в папке `ui/`. Такие компоненты не экспортируются наружу и используются только локально.
|
||
|
||
Вложенные компоненты подчиняются тем же правилам по структуре, именованию и стилю (включая папку `styles/` для их стилей).
|
||
|
||
<!-- /applied/page-level -->
|
||
## 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 (
|
||
<html lang="ru" suppressHydrationWarning>
|
||
<body>
|
||
<Providers>
|
||
{children}
|
||
</Providers>
|
||
</body>
|
||
</html>
|
||
)
|
||
}
|
||
```
|
||
|
||
Вложенный layout — для секции с общей обёрткой (sidebar, header):
|
||
|
||
```tsx
|
||
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`.
|
||
|
||
```tsx
|
||
import type { Metadata } from 'next'
|
||
import { HomeScreen } from '@/screens/home'
|
||
|
||
export const metadata: Metadata = {
|
||
title: 'Главная',
|
||
description: 'Главная страница приложения',
|
||
}
|
||
|
||
export default function HomePage() {
|
||
return <HomeScreen />
|
||
}
|
||
```
|
||
|
||
С параметрами маршрута:
|
||
|
||
```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 <ProfileScreen id={id} />
|
||
}
|
||
```
|
||
|
||
Каждая страница должна содержать `metadata` с `title` — он подставится в шаблон из корневого layout: `Профиль | App`.
|
||
|
||
### loading.tsx
|
||
|
||
Состояние загрузки. Показывается пока загружается контент страницы.
|
||
|
||
```tsx
|
||
export default function Loading() {
|
||
return <div>Загрузка...</div>
|
||
}
|
||
```
|
||
|
||
### 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<ErrorPageProps> = ({ error, reset }) => {
|
||
return <ErrorScreen error={error} reset={reset} />
|
||
}
|
||
|
||
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 <NotFoundScreen />
|
||
}
|
||
```
|
||
|
||
### template.tsx
|
||
|
||
Аналог layout, но пересоздаётся при каждой навигации (не сохраняет состояние). Используется редко — для анимаций переходов между страницами.
|
||
|
||
```tsx
|
||
import type { PropsWithChildren } from 'react'
|
||
|
||
export default function Template({ children }: PropsWithChildren) {
|
||
return <div>{children}</div>
|
||
}
|
||
```
|
||
|
||
<!-- /applied/templates-generation -->
|
||
<!-- @formatter:off -->
|
||
::: 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<HTMLDivElement> {}
|
||
```
|
||
|
||
```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 (
|
||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||
{{name.kebabCase}}
|
||
</div>
|
||
)
|
||
}
|
||
```
|
||
|
||
```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` | Стор |
|
||
|
||
:::
|
||
|
||
<!-- /applied/styles -->
|
||
## Стили
|
||
|
||
Раздел описывает правила написания 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.
|
||
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
|
||
|
||
<!-- /applied/svg-sprites -->
|
||
## SVG-спрайты |