Compare commits

..

5 Commits

Author SHA1 Message Date
bf1781f143 docs: переработать архитектуру и прикладные разделы компонента и модуля
All checks were successful
CI/CD Pipeline / docker (push) Successful in 45s
CI/CD Pipeline / deploy (push) Successful in 7s
- обновлена каноническая спецификация SLM Design из slm-design.gromlab.ru
- файлы архитектуры перенесены из reference/ в basics/architecture/
- добавлена предупреждающая плашка о локальной копии архитектуры
- infrastructure/ переименован в infra/ по канону спецификации
- прикладной раздел «Компонент» переписан: определение заменено ссылкой на архитектуру, добавлен пример UserStatus с типами, стилями и JSDoc
- прикладной раздел «Модуль» переписан: показаны три типа модулей (UI, бизнес, инфраструктурный), добавлен пример фабрики бизнес-модуля
- добавлен запрет ручного создания компонентов: только через кодогенератор
- обновлён раздел типизации: возвращаемый тип React-компонентов не указывается
- обновлён шаблон компонента в templates-create: суффикс -props.type.ts
- обновлены ссылки в page-level, project-structure, aliases
- сгенерированы llms.txt, llms-full.txt, README и публичные копии
2026-05-03 04:22:26 +03:00
e835210d6d docs: переработать раздел REST-клиента и стратегий получения данных
All checks were successful
CI/CD Pipeline / docker (push) Successful in 45s
CI/CD Pipeline / deploy (push) Successful in 7s
- Добавлен обзор REST с разделением на «Создание клиента» и «Использование»
- Добавлена страница создания клиента с описанием структуры модуля
- Переписана автогенерация: npx без --swr, расширения типов вынесены в types/
- Ручной клиент сокращён до шаблона по файлам
- Добавлены GET-хуки REST-клиента с контрактом useGet..., key-функциями и isReady
- Добавлена страница выбора стратегий с приоритетом ISR перед SSR
- Добавлены стратегии: серверный await, параллельные запросы, передача промиса,
  начальные данные для клиентских хуков, клиентский GET-хук, business-композиция
- Уточнено влияние серверного await и SWR fallback на режим рендера
- Удалены устаревшие страницы fetching/server.md и fetching/client.md
- Обновлён generate-llms.ts: очистка stale-файлов перед копированием
- Обновлены сайдбар, MAP.md, data/index.md, page-level.md
2026-04-30 16:01:18 +03:00
f4e78e3227 docs: обновить прикладные разделы
All checks were successful
CI/CD Pipeline / docker (push) Successful in 1m2s
CI/CD Pipeline / deploy (push) Successful in 7s
- обновлён порядок и состав прикладных разделов
- добавлены разделы про компоненты, изображения, шрифты и локализацию
- уточнены правила модулей, страниц, сегментов и именования
- удалены устаревшие пустые разделы и обзор документации
2026-04-29 20:52:01 +03:00
c8d52e9128 docs: добавить введение для SVG-спрайтов, переименовать компоненты в модуль
Some checks failed
CI/CD Pipeline / docker (push) Failing after 8m29s
CI/CD Pipeline / deploy (push) Has been skipped
- Добавлен svg-sprites-intro.md с описанием проблем и решения
- Добавлен шаг preload спрайта в layout в настройке SVG-спрайтов
- Добавлен раздел «Дальше» в настройку SVG-спрайтов
- Исправлена ссылка на стандартный конфиг (якорь)
- components.md переименован в module.md
- Создан пустой component.md (в разработке)
- Обновлён сайдбар, MAP.md
2026-04-29 13:44:48 +03:00
9f1bc0cc32 refactor(docs): объединить setup/ и usage/ в прикладные разделы
Some checks failed
CI/CD Pipeline / docker (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
- Директории setup/ и usage/ объединены в applied/ с подпапками для парных разделов
- Раздел «Работа с данными» вынесен на верхний уровень в data/
- Шаблоны генерации разделены на 4 файла: введение, настройка, создание, использование
- Обновлён сайдбар, CONTRIBUTING.md, MAP.md и все внутренние ссылки
- Обновлены требования и содержимое разделов (стили, шаблоны, SVG-спрайты)
2026-04-29 11:25:58 +03:00
64 changed files with 3118 additions and 1926 deletions

View File

@@ -19,9 +19,9 @@ const sidebar = [
collapsed: true, collapsed: true,
items: [ items: [
{ text: 'Обзор', link: '/docs/basics/architecture/' }, { text: 'Обзор', link: '/docs/basics/architecture/' },
{ text: 'Слои', link: '/docs/basics/architecture/reference/layers' }, { text: 'Слои', link: '/docs/basics/architecture/layers' },
{ text: 'Модули', link: '/docs/basics/architecture/reference/modules' }, { text: 'Модули', link: '/docs/basics/architecture/modules' },
{ text: 'Сегменты', link: '/docs/basics/architecture/reference/segments' }, { text: 'Сегменты', link: '/docs/basics/architecture/segments' },
], ],
}, },
{ text: 'Стиль кода', link: '/docs/basics/code-style' }, { text: 'Стиль кода', link: '/docs/basics/code-style' },
@@ -38,63 +38,87 @@ const sidebar = [
], ],
}, },
{ {
text: 'Настройка', text: 'Работа с данными',
// collapsed: true,
items: [ items: [
{ text: 'Алиасы импортов', link: '/docs/setup/aliases' }, { text: 'Введение', link: '/docs/data/' },
{ text: 'Biome', link: '/docs/setup/biome' },
{ text: 'PostCSS', link: '/docs/setup/postcss' },
{ text: 'Стили', link: '/docs/setup/styles' },
{ text: 'SVG-спрайты', link: '/docs/setup/svg-sprites' },
{ text: 'Шаблоны генерации', link: '/docs/setup/templates' },
{ text: 'VS Code', link: '/docs/setup/vscode' },
],
},
{
text: 'Использование',
items: [
{ text: 'Структура проекта', link: '/docs/usage/project-structure' },
{ text: 'Компоненты', link: '/docs/usage/components' },
{ text: 'Страницы (App Router)', link: '/docs/usage/page-level' },
{ text: 'Шаблоны и генерация кода', link: '/docs/usage/templates-generation' },
{ text: 'Стили', link: '/docs/usage/styles' },
// Неактивные разделы: страницы существуют, но пока пустые.
// Оставляем в sidebar без `link`, чтобы видеть план, но без перехода.
{ text: 'Изображения · в разработке' },
{ text: 'SVG-спрайты', link: '/docs/usage/svg-sprites' },
{ text: 'Видео · в разработке' },
{ text: 'Stores · в разработке' },
{ text: 'Хуки · в разработке' },
{ text: 'Шрифты · в разработке' },
{ text: 'Локализация · в разработке' },
],
},
{
text: 'Данные',
items: [
{ text: 'Введение', link: '/docs/usage/data/' },
{ {
text: 'REST', text: 'REST',
collapsed: true, collapsed: true,
items: [ items: [
{ text: 'Обзор', link: '/docs/data/rest/' },
{ {
text: 'Клиенты', text: 'Создание клиента',
collapsed: true, collapsed: true,
items: [ items: [
{ text: 'Автоматическая генерация', link: '/docs/usage/data/rest/clients/auto' }, { text: 'Обзор', link: '/docs/data/rest/clients/' },
{ text: 'Ручное создание', link: '/docs/usage/data/rest/clients/manual' }, { text: 'Автогенерация из OpenAPI', link: '/docs/data/rest/clients/auto' },
{ text: 'Ручное создание', link: '/docs/data/rest/clients/manual' },
{ text: 'GET-хуки REST-клиента', link: '/docs/data/rest/clients/hooks' },
], ],
}, },
{ {
text: 'Получение данных', text: 'Использование',
collapsed: true, collapsed: true,
items: [ items: [
{ text: 'Серверные компоненты', link: '/docs/usage/data/rest/fetching/server' }, { text: 'Стратегии получения данных', link: '/docs/data/rest/strategies/' },
{ text: 'Клиентские компоненты', link: '/docs/usage/data/rest/fetching/client' }, { text: 'Серверный await', link: '/docs/data/rest/strategies/server-await' },
{ text: 'Параллельные серверные запросы', link: '/docs/data/rest/strategies/parallel-server-requests' },
{ text: 'Передача промиса ниже', link: '/docs/data/rest/strategies/pass-promise-down' },
{ text: 'Начальные данные для клиентских хуков', link: '/docs/data/rest/strategies/client-hooks-initial-data' },
{ text: 'Клиентский GET-хук', link: '/docs/data/rest/strategies/client-get-hook' },
{ text: 'Business-композиция', link: '/docs/data/rest/strategies/business-composition' },
], ],
}, },
], ],
}, },
{ text: 'Realtime', link: '/docs/usage/data/realtime' }, { text: 'Realtime', link: '/docs/data/realtime' },
],
},
{
text: 'Прикладные разделы',
items: [
{ text: 'Структура проекта', link: '/docs/applied/project-structure' },
{ text: 'Страницы', link: '/docs/applied/page-level' },
{ text: 'Компонент', link: '/docs/applied/component' },
{ text: 'Модуль', link: '/docs/applied/module' },
{
text: 'Стили',
collapsed: true,
items: [
{ text: 'Настройка', link: '/docs/applied/styles/styles-setup' },
{ text: 'Использование', link: '/docs/applied/styles/styles-usage' },
],
},
{
text: 'SVG-спрайты',
collapsed: true,
items: [
{ text: 'Введение', link: '/docs/applied/svg-sprites/svg-sprites-intro' },
{ text: 'Настройка', link: '/docs/applied/svg-sprites/svg-sprites-setup' },
{ text: 'Использование', link: '/docs/applied/svg-sprites/svg-sprites-usage' },
],
},
{ text: 'Изображения', link: '/docs/applied/images' },
{ text: 'Шрифты', link: '/docs/applied/fonts' },
{ text: 'Алиасы импортов', link: '/docs/applied/aliases' },
{
text: 'Шаблоны генерации',
collapsed: true,
items: [
{ text: 'Введение', link: '/docs/applied/templates/templates-intro' },
{ text: 'Настройка', link: '/docs/applied/templates/templates-setup' },
{ text: 'Создание шаблонов', link: '/docs/applied/templates/templates-create' },
{ text: 'Использование', link: '/docs/applied/templates/templates-usage' },
],
},
{ text: 'Biome', link: '/docs/applied/biome' },
{ text: 'PostCSS', link: '/docs/applied/postcss' },
{ text: 'VS Code', link: '/docs/applied/vscode' },
{ text: 'Локализация', link: '/docs/applied/localization' },
// Неактивные разделы: страницы существуют, но пока пустые.
// Оставляем в sidebar без `link`, чтобы видеть план, но без перехода.
{ text: 'Stores · в разработке' }
], ],
}, },
]; ];

View File

@@ -37,28 +37,35 @@ docs/
│ ├── from-template.md │ ├── from-template.md
│ ├── manual.md │ ├── manual.md
│ └── nextjs.md │ └── nextjs.md
├── setup/ # Настройка: разовая настройка инструментов ├── data/ # Работа с данными
│ ├── aliases.md │ ├── index.md
│ ├── biome.md │ ├── realtime.md
── postcss.md ── rest/
│ ├── styles.md └── applied/ # Прикладные разделы: настройка и использование
│ ├── svg-sprites.md
│ ├── templates.md
│ └── vscode.md
└── usage/ # Использование: повседневная работа
├── project-structure.md ├── project-structure.md
├── components.md
├── page-level.md ├── page-level.md
├── templates-generation.md ├── component.md
├── styles.md ├── module.md
├── images-sprites.md ├── styles/ # Стили: настройка + использование
├── svg-sprites.md ├── styles-setup.md
├── video.md │ └── styles-usage.md
├── data/ ├── svg-sprites/ # SVG-спрайты: введение + настройка + использование
├── stores.md ├── svg-sprites-intro.md
├── hooks.md │ ├── svg-sprites-setup.md
│ └── svg-sprites-usage.md
├── images.md
├── fonts.md ├── fonts.md
── localization.md ── aliases.md
├── templates/ # Шаблоны генерации: введение + настройка + использование
│ ├── templates-intro.md
│ ├── templates-setup.md
│ ├── templates-create.md
│ └── templates-usage.md
├── biome.md
├── postcss.md
├── vscode.md
├── localization.md
└── stores.md
.vitepress/ .vitepress/
└── config.ts # Конфигурация VitePress, сайдбар └── config.ts # Конфигурация VitePress, сайдбар
generate-llms.ts # Скрипт генерации llms.txt и README generate-llms.ts # Скрипт генерации llms.txt и README
@@ -70,7 +77,7 @@ generate-llms.ts # Скрипт генерации llms.txt и R
### Добавление нового раздела ### Добавление нового раздела
1. Создать `.md`-файл в нужной папке: `basics/`, `creating-project/`, 1. Создать `.md`-файл в нужной папке: `basics/`, `creating-project/`,
`setup/` или `usage/`. или `applied/`.
2. Добавить пункт в сайдбар — `.vitepress/config.ts`. 2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
Сайдбар — единственный источник порядка и группировки для `llms.txt`. Сайдбар — единственный источник порядка и группировки для `llms.txt`.
3. Запустить `npm run llms` для обновления `llms.txt` и README. 3. Запустить `npm run llms` для обновления `llms.txt` и README.
@@ -100,36 +107,27 @@ generate-llms.ts # Скрипт генерации llms.txt и R
Сценарии запуска нового проекта целиком: из шаблона, вручную, чистая Сценарии запуска нового проекта целиком: из шаблона, вручную, чистая
установка фреймворка. Раздел описывает порядок шагов на уровне всего установка фреймворка. Раздел описывает порядок шагов на уровне всего
проекта; детали отдельных инструментов лежат в `setup/`. проекта; детали отдельных инструментов лежат в `applied/`.
**Граница:** не дублирует разделы `setup/`. Ссылается на них как на **Граница:** не дублирует разделы `applied/`. Ссылается на них как на
шаги в общем сценарии. шаги в общем сценарии.
### Настройка (`setup/`) ### Прикладные разделы (`applied/`)
**Отвечает на вопрос:** «Как поставить и сконфигурировать инструмент **Отвечает на вопрос:** «Как поставить инструмент и как им пользоваться?»
в новом проекте?»
Разовая установка отдельного инструмента или подсистемы (линтер, Прикладные разделы объединяют настройку и использование инструментов
CSS-процессор, генератор спрайтов, шаблоны). Каждый раздел — и подсистем. Каждый раздел — самостоятельная предметная область.
самостоятельная подсистема. Выполняется один раз при заведении
проекта или при смене мажорной версии инструмента.
Типичная структура `setup/`-страницы: требования → установка (шаги) → Разделы делятся на два типа:
конфиг → проверка.
**Граница:** `setup/` — про настройку, `usage/` — про написание кода 1. **Только настройка** — разовая установка инструмента (линтер,
с использованием уже настроенного инструмента. CSS-процессор, алиасы). Файл без суффикса: `biome.md`, `postcss.md`.
### Использование (`usage/`) 2. **Настройка + использование** — область, требующая и установки,
и повседневных правил. Два файла с суффиксами: `styles-setup.md`
**Отвечает на вопрос:** «Как этим пользоваться в коде?» (настройка) и `styles-usage.md` (использование). В сайдбаре
оборачиваются в collapsed-группу.
Повседневная работа: как писать компоненты, стили, как получать данные,
как работать со сторами, локализацией, ассетами. Полное описание
конкретной области: структура файлов, правила, именование, типизация, примеры.
Шаблон страницы описан ниже в секции «Структура прикладного раздела».
**Граница:** прикладной раздел не дублирует базовые правила. Если правило **Граница:** прикладной раздел не дублирует базовые правила. Если правило
уже описано в `basics/` — прикладной раздел ссылается на него, но не уже описано в `basics/` — прикладной раздел ссылается на него, но не
@@ -137,9 +135,9 @@ CSS-процессор, генератор спрайтов, шаблоны). К
## Структура прикладного раздела ## Структура прикладного раздела
Шаблон ниже относится к разделам `usage/` (повседневная работа). Шаблон ниже относится к usage-страницам прикладных разделов (`applied/*-usage.md`).
Разделы `setup/` и `creating-project/` имеют другую структуру — Setup-страницы (`applied/*-setup.md`) и `creating-project/` имеют другую
ориентированную на пошаговую установку (требования → установка → структуру — ориентированную на пошаговую установку (требования → установка →
проверка). проверка).
Шаблон описывает все допустимые секции. Раздел включает только те, Шаблон описывает все допустимые секции. Раздел включает только те,

View File

@@ -1,174 +0,0 @@
# Обзор страниц документации
Список всех `.md`-страниц в `docs/docs/` в порядке сайдбара (`.vitepress/config.ts`).
Поля: путь к файлу, заголовок (`h1`), описание (абзац под заголовком).
## Главная
### docs/docs/index.md
**Заголовок:** NextJS Style Guide
**Описание:** Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
## Подсказки
### docs/docs/workflow.md
**Заголовок:** Подсказки
**Описание:** Короткие ответы на типовые вопросы и решения для спорных ситуаций.
## Базовые правила
### docs/docs/basics/tech-stack.md
**Заголовок:** Технологии и библиотеки
**Описание:** Какие библиотеки и инструменты используются в проекте.
### docs/docs/basics/naming.md
**Заголовок:** Именование
**Описание:** Как называть переменные, файлы и прочие сущности в коде.
### docs/docs/basics/architecture/index.md
**Заголовок:** SLM Design
**Описание:** Архитектурный подход проекта: что такое SLM и как он устроен.
### docs/docs/basics/architecture/reference/layers.md
**Заголовок:** Слои SLM
**Описание:** Из каких слоёв состоит SLM-архитектура и как они связаны.
### docs/docs/basics/architecture/reference/modules.md
**Заголовок:** Модули SLM
**Описание:** Что такое модуль в SLM-архитектуре и как он устроен.
### docs/docs/basics/architecture/reference/segments.md
**Заголовок:** Сегменты SLM
**Описание:** Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
### docs/docs/basics/code-style.md
**Заголовок:** Стиль кода
**Описание:** Как оформляется код в проекте.
### docs/docs/basics/documentation.md
**Заголовок:** Документирование
**Описание:** Что и как документировать в коде.
### docs/docs/basics/typing.md
**Заголовок:** Типизация
**Описание:** Как типизируется код в проекте.
## Создание проекта
### docs/docs/creating-project/from-template.md
**Заголовок:** Создание проекта из шаблона
**Описание:** Создание нового проекта на основе готового шаблона.
### docs/docs/creating-project/manual.md
**Заголовок:** Создание проекта вручную
**Описание:** Поэтапное создание нового проекта без использования шаблона.
### docs/docs/creating-project/nextjs.md
**Заголовок:** Чистая установка Next.js
**Описание:** Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
## Настройка
### docs/docs/setup/aliases.md
**Заголовок:** Алиасы импортов
**Описание:** Какие алиасы импортов есть в проекте и как ими пользоваться.
### docs/docs/setup/biome.md
**Заголовок:** Biome
**Описание:** Установка и настройка линтера-форматтера в новом проекте.
### docs/docs/setup/postcss.md
**Заголовок:** PostCSS
**Описание:** Установка и настройка CSS-процессора в новом проекте.
### docs/docs/setup/styles.md
**Заголовок:** Стили
**Описание:** Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
### docs/docs/setup/svg-sprites.md
**Заголовок:** SVG-спрайты
**Описание:** Подключение SVG-спрайтов в новом проекте.
### docs/docs/setup/templates.md
**Заголовок:** Шаблоны генерации
**Описание:** Подключение шаблонов кодогенерации в новом проекте.
### docs/docs/setup/vscode.md
**Заголовок:** VS Code
**Описание:** Единые настройки редактора и расширений для команды.
## Использование
### docs/docs/usage/project-structure.md
**Заголовок:** Структура проекта
**Описание:** Из чего состоит проект и где что лежит.
### docs/docs/usage/components.md
**Заголовок:** Компоненты
**Описание:** Как устроен и пишется React-компонент в проекте.
### docs/docs/usage/page-level.md
**Заголовок:** Файлы роутинга
**Описание:** Что должно лежать в файлах роутинга, а что — в экранах.
### docs/docs/usage/templates-generation.md
**Заголовок:** Шаблоны и генерация кода
**Описание:** Как устроены шаблоны кодогенерации и как ими пользоваться.
### docs/docs/usage/styles.md
**Заголовок:** Стили
**Описание:** Как пишутся стили в проекте.
### docs/docs/usage/images-sprites.md
**Заголовок:** —
**Описание:** _(файл пустой)_
### docs/docs/usage/svg-sprites.md
**Заголовок:** SVG-спрайты
**Описание:** Как добавлять и использовать SVG-иконки в коде.
### docs/docs/usage/video.md
**Заголовок:** —
**Описание:** _(файл пустой)_
### docs/docs/usage/stores.md
**Заголовок:** —
**Описание:** _(файл пустой)_
### docs/docs/usage/hooks.md
**Заголовок:** —
**Описание:** _(файл пустой)_
### docs/docs/usage/fonts.md
**Заголовок:** —
**Описание:** _(файл пустой)_
### docs/docs/usage/localization.md
**Заголовок:** —
**Описание:** _(файл пустой)_
## Данные
### docs/docs/usage/data/index.md
**Заголовок:** Источники данных
**Описание:** Какие источники данных используются в проекте и как с ними работать.
### docs/docs/usage/data/rest/clients/auto.md
**Заголовок:** Автогенерация REST-клиента
**Описание:** Генерация REST-клиента из OpenAPI-спецификации.
### docs/docs/usage/data/rest/clients/manual.md
**Заголовок:** Ручное создание REST-клиента
**Описание:** Создание REST-клиента вручную, когда нет OpenAPI-спецификации.
### docs/docs/usage/data/rest/fetching/server.md
**Заголовок:** Серверные компоненты
**Описание:** Получение REST-данных в серверных компонентах.
### docs/docs/usage/data/rest/fetching/client.md
**Заголовок:** Клиентские компоненты
**Описание:** Получение REST-данных в клиентских компонентах.
### docs/docs/usage/data/realtime.md
**Заголовок:** Realtime
**Описание:** Работа с push-данными от сервера: подписки и события.

View File

@@ -22,9 +22,9 @@ description: Что AI-агент обязан прочитать перед н
### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО) ### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО)
* [Архитектура: Обзор](./basics/architecture/index.md) * [Архитектура: Обзор](./basics/architecture/index.md)
* [Архитектура: Слои](./basics/architecture/reference/layers.md) * [Архитектура: Слои](./basics/architecture/layers.md)
* [Архитектура: Модули](./basics/architecture/reference/modules.md) * [Архитектура: Модули](./basics/architecture/modules.md)
* [Архитектура: Сегменты](./basics/architecture/reference/segments.md) * [Архитектура: Сегменты](./basics/architecture/segments.md)
**Архитектура — это самое важное в проекте.** **Архитектура — это самое важное в проекте.**

View File

@@ -12,9 +12,9 @@
- [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте. - [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте.
- [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде. - [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде.
- [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен. - [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен.
- [Архитектура: Слои](./basics/architecture/reference/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны. - [Архитектура: Слои](./basics/architecture/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны.
- [Архитектура: Модули](./basics/architecture/reference/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен. - [Архитектура: Модули](./basics/architecture/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен.
- [Архитектура: Сегменты](./basics/architecture/reference/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают. - [Архитектура: Сегменты](./basics/architecture/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
- [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте. - [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте.
- [Документирование](./basics/documentation.md) — Что и как документировать в коде. - [Документирование](./basics/documentation.md) — Что и как документировать в коде.
- [Типизация](./basics/typing.md) — Как типизируется код в проекте. - [Типизация](./basics/typing.md) — Как типизируется код в проекте.
@@ -25,30 +25,42 @@
- [По гайду вручную](./creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона. - [По гайду вручную](./creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона.
- [Чистый Next.js](./creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку. - [Чистый Next.js](./creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
## Настройка ## Работа с данными
- [Алиасы импортов](./setup/aliases.md) — Какие алиасы импортов есть в проекте и как ими пользоваться. - [Введение](./data/index.md) — Какие источники данных используются в проекте и как с ними работать.
- [Biome](./setup/biome.md) — Установка и настройка линтера-форматтера в новом проекте. - [REST](./data/rest/index.md) — Как правильно работать с REST API в проекте.
- [PostCSS](./setup/postcss.md) — Установка и настройка CSS-процессора в новом проекте. - [REST: Создание клиента](./data/rest/clients/index.md) — Как выбрать способ создания REST-клиента и где размещать его части.
- [Стили](./setup/styles.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили. - [REST: Создание клиента: Автогенерация из OpenAPI](./data/rest/clients/auto.md) — Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
- [SVG-спрайты](./setup/svg-sprites.md) — Подключение SVG-спрайтов в новом проекте. - [REST: Создание клиента: Ручное создание](./data/rest/clients/manual.md) — Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
- [Шаблоны генерации](./setup/templates.md) — Подключение шаблонов кодогенерации в новом проекте. - [REST: Создание клиента: GET-хуки REST-клиента](./data/rest/clients/hooks.md) — Прозрачные SWR-обёртки над GET-методами REST-клиента.
- [VS Code](./setup/vscode.md) — Единые настройки редактора и расширений для команды. - [REST: Использование: Стратегии получения данных](./data/rest/strategies/index.md) — Как выбрать способ получения REST-данных в зависимости от места и сценария.
- [REST: Использование: Серверный await](./data/rest/strategies/server-await.md) — Получение REST-данных на сервере прямым await метода клиента.
- [REST: Использование: Параллельные серверные запросы](./data/rest/strategies/parallel-server-requests.md) — Как запускать независимые REST-запросы на сервере без waterfall.
- [REST: Использование: Передача промиса ниже](./data/rest/strategies/pass-promise-down.md) — Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
- [REST: Использование: Начальные данные для клиентских хуков](./data/rest/strategies/client-hooks-initial-data.md) — Как передать серверный промис в SWR fallback, чтобы клиентские GET-хуки получили начальные данные.
- [REST: Использование: Клиентский GET-хук](./data/rest/strategies/client-get-hook.md) — Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
- [REST: Использование: Business-композиция](./data/rest/strategies/business-composition.md) — Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
- [Realtime](./data/realtime.md) — Работа с push-данными от сервера: подписки и события.
## Использование ## Прикладные разделы
- [Структура проекта](./usage/project-structure.md) — Из чего состоит проект и где что лежит. - [Структура проекта](./applied/project-structure.md) — Из чего состоит проект и где что лежит.
- [Компоненты](./usage/components.md) — Как устроен и пишется React-компонент в проекте. - [Страницы](./applied/page-level.md) — Как работать со страницами и другими файлами роутинга Next.js App Router.
- [Страницы (App Router)](./usage/page-level.md) — Что должно лежать в файлах роутинга, а что — в экранах. - [Компонент](./applied/component.md) — Как создавать React-компоненты внутри SLM-модулей.
- [Шаблоны и генерация кода](./usage/templates-generation.md) — Как устроены шаблоны кодогенерации и как ими пользоваться. - [Модуль](./applied/module.md) — Как создавать и организовывать SLM-модули в проекте.
- [Стили](./usage/styles.md) — Как пишутся стили в проекте. - [Стили: Настройка](./applied/styles/styles-setup.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
- [SVG-спрайты](./usage/svg-sprites.md) — Как добавлять и использовать SVG-иконки в коде. - [Стили: Использование](./applied/styles/styles-usage.md) — Как пишутся стили в проекте.
- [SVG-спрайты](./applied/svg-sprites/svg-sprites-intro.md) — Что такое SVG-спрайты и какие проблемы они решают.
## Данные - [SVG-спрайты: Настройка](./applied/svg-sprites/svg-sprites-setup.md) — Подключение SVG-спрайтов в новом проекте.
- [SVG-спрайты: Использование](./applied/svg-sprites/svg-sprites-usage.md) — Как добавлять и использовать SVG-иконки в коде.
- [Введение](./usage/data/index.md) — Какие источники данных используются в проекте и как с ними работать. - [Изображения](./applied/images.md) — Как подключать изображения через Next.js Image в проекте.
- [REST: Клиенты: Автоматическая генерация](./usage/data/rest/clients/auto.md) — Генерация REST-клиента из OpenAPI-спецификации. - [Шрифты](./applied/fonts.md) — Как подключать шрифты через Next.js Font в проекте.
- [REST: Клиенты: Ручное создание](./usage/data/rest/clients/manual.md) — Создание REST-клиента вручную, когда нет OpenAPI-спецификации. - [Алиасы импортов](./applied/aliases.md) — Какие алиасы импортов есть в проекте и как ими пользоваться.
- [REST: Получение данных: Серверные компоненты](./usage/data/rest/fetching/server.md) — Получение REST-данных в серверных компонентах. - [Шаблоны генерации](./applied/templates/templates-intro.md) — Что такое шаблоны кодогенерации и какие проблемы они решают.
- [REST: Получение данных: Клиентские компоненты](./usage/data/rest/fetching/client.md) — Получение REST-данных в клиентских компонентах. - [Шаблоны генерации: Настройка](./applied/templates/templates-setup.md) — Первичная установка шаблонов кодогенерации в проект.
- [Realtime](./usage/data/realtime.md) — Работа с push-данными от сервера: подписки и события. - [Шаблоны генерации: Создание шаблонов](./applied/templates/templates-create.md) — Структура шаблонов, синтаксис переменных и примеры.
- [Шаблоны генерации: Использование](./applied/templates/templates-usage.md) — Генерация файлов из шаблонов через VS Code плагин и CLI.
- [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте.
- [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте.
- [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды.
- [Локализация](./applied/localization.md) — Как организовать локализацию как infrastructure-модуль.

View File

@@ -36,7 +36,7 @@ keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app,
- **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля. - **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля.
- **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля. - **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля.
- **Префикс `@/` не используется.** Имя слоя — само по себе адрес. - **Префикс `@/` не используется.** Имя слоя — само по себе адрес.
- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](/docs/basics/architecture/reference/layers)). - **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](/docs/basics/architecture/layers)).
**Хорошо** **Хорошо**

View File

@@ -78,4 +78,4 @@ keywords: [biome, линтер, форматтер, lint, format, biome.json, "@
## Интеграция с VS Code ## Интеграция с VS Code
Расширение `biomejs.biome` и автоформатирование при сохранении настраиваются в [Настройка VS Code](/docs/setup/vscode). Расширение `biomejs.biome` и автоформатирование при сохранении настраиваются в [VS Code](/docs/applied/vscode).

View File

@@ -0,0 +1,165 @@
---
title: Компонент
description: Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля.
---
# Компонент
Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля.
## Назначение
Архитектурное определение компонента описано в разделе [Модули → Компонент](/docs/basics/architecture/modules#компонент), а структура сегмента `ui/` — в разделе [Сегменты → ui/](/docs/basics/architecture/segments#сегмент-ui).
Эта страница не повторяет архитектурные ограничения. Она показывает, каким должен быть результат генерации компонента: структура папки, `.tsx`, типы, стили и локальный экспорт.
::: danger Компоненты не создаются вручную
Компоненты в проекте создаются только через кодогенератор: через [VS Code](/docs/applied/templates/templates-usage#через-vs-code) или [CLI](/docs/applied/templates/templates-usage#через-cli).
Ручное создание компонента запрещено. Это грубое нарушение правил работы в проекте для разработчика и AI-ассистента.
Если в проекте нет шаблона `.templates/component`, сначала создайте шаблон по разделу [Создание шаблонов](/docs/applied/templates/templates-create), и только потом генерируйте компонент на его основе.
:::
## Создание
1. Проверьте, что в проекте есть шаблон `.templates/component`.
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](/docs/applied/templates/templates-create).
3. Сгенерируйте компонент через [VS Code или CLI](/docs/applied/templates/templates-usage).
Структура и код ниже показывают ожидаемый результат генерации. Их нельзя использовать как инструкцию для ручного создания файлов.
## Структура
Компонент размещается в `ui/{component-name}/` родительского модуля.
Для каждого компонента обязательны `.tsx`, типы, стили и локальный `index.ts`.
```text
user-card/
└── ui/
└── user-status/
├── styles/
│ └── user-status.module.css
├── types/
│ └── user-status-props.type.ts
├── user-status.tsx
└── index.ts
```
## Реализация
Пример ниже показывает файлы базового компонента.
### Типы
Файл типов делится на три части:
- `UserStatusParams` — собственные параметры компонента. Здесь лежат только данные, которые нужны именно этому компоненту.
- `RootAttrs` — параметры корневой обёртки: `div`, `span`, `a`, `button` или другого HTML-элемента. Если компонент сам управляет `children`, они исключаются через `Omit`.
- `UserStatusProps` — итоговые пропсы компонента. Тип объединяет собственные параметры и параметры корневой обёртки.
Собственные параметры и их поля документируются по правилам раздела [Документирование → Типы, интерфейсы, enum](/docs/basics/documentation#типы-интерфейсы-enum).
`user-card/ui/user-status/types/user-status-props.type.ts`
```ts
import type { ComponentPropsWithoutRef } from 'react'
/**
* Параметры UserStatus.
*/
export type UserStatusParams = {
/** Текст статуса пользователя. */
label: string
/** Доступен ли пользователь сейчас. */
isOnline: boolean
}
/** Атрибуты корневого элемента без children. */
type RootAttrs = Omit<ComponentPropsWithoutRef<'span'>, 'children'>
export type UserStatusProps = RootAttrs & UserStatusParams
```
### TSX
В `.tsx` лежит только сам компонент:
- Компонент объявляется через `const` и именованный экспорт.
- `React.FC` не используется.
- Параметры компонента типизируются через `Props`.
- Возвращаемый тип не указывается: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
- JSDoc-комментарий обязателен и пишется по правилам раздела [Документирование → Компоненты](/docs/basics/documentation#компоненты).
- Пропсы деструктурируются в теле компонента, а не в сигнатуре.
- Из пропсов обязательно выделяются `className` и `...rootAttrs`.
- Функция конкатенации CSS-классов импортируется и именуется `cl`.
- Корневой CSS-класс всегда называется `.root`.
Комментарий описывает назначение и сценарии применения компонента, а не DOM-разметку или внутреннюю реализацию.
`className` — внешний CSS-класс, который родитель может передать компоненту. `rootAttrs` — остальные атрибуты корневой обёртки: `id`, `aria-*`, `data-*`, обработчики событий и другие HTML-атрибуты. Они прокидываются на корневой DOM-элемент компонента.
`.root` нужен, чтобы в DevTools быстро находить корневой DOM-узел компонента и одинаково подключать внешний `className` к реальному корню.
`user-card/ui/user-status/user-status.tsx`
```tsx
import cl from 'clsx'
import type { UserStatusProps } from './types/user-status-props.type'
import styles from './styles/user-status.module.css'
/**
* Статус пользователя в карточке профиля.
*
* Используется для:
* - отображения текущей доступности пользователя
* - визуального выделения онлайн- и офлайн-состояний
*/
export const UserStatus = (props: UserStatusProps) => {
const { label, isOnline, className, ...rootAttrs } = props
return (
<span
{...rootAttrs}
className={cl(styles.root, isOnline && styles.online, className)}
>
{label}
</span>
)
}
```
### Стили
`user-card/ui/user-status/styles/user-status.module.css`
```css
.root {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--color-text-muted);
}
.root::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
.online {
color: var(--color-success);
}
```
### Локальный экспорт
`user-card/ui/user-status/index.ts`
```ts
export { UserStatus } from './user-status'
export type { UserStatusProps } from './types/user-status-props.type'
```

128
docs/docs/applied/fonts.md Normal file
View File

@@ -0,0 +1,128 @@
---
title: Шрифты
description: Как подключать шрифты через Next.js Font в проекте.
---
# Шрифты
Как подключать шрифты через Next.js Font в проекте.
## Назначение
Шрифты подключаются через `next/font`. Это стандартный способ Next.js: шрифты загружаются без ручных `<link>`, `@font-face` и настройки preconnect.
Шрифт подключается в точке инициализации приложения, а в CSS используется через переменную.
## Google Fonts
```tsx
// src/app/layout.tsx
import type { ReactNode } from 'react'
import { Inter } from 'next/font/google'
import 'shared/styles/global.css'
const inter = Inter({
subsets: ['latin', 'cyrillic'],
variable: '--font-main',
display: 'swap',
})
type RootLayoutProps = {
children: ReactNode
}
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="ru" className={inter.variable}>
<body>{children}</body>
</html>
)
}
```
```css
/* src/shared/styles/global.css */
body {
font-family: var(--font-main), system-ui, sans-serif;
}
```
## Локальные шрифты
Каждый локальный шрифт размещается в отдельной папке внутри `src/shared/fonts/`. В этой же папке лежит `.font.ts`, где объявляется `localFont`.
```text
src/shared/fonts/
└── roboto/
├── roboto.font.ts
├── Roboto-Regular.woff2
├── Roboto-Italic.woff2
├── Roboto-Bold.woff2
└── Roboto-BoldItalic.woff2
```
```ts
// src/shared/fonts/roboto/roboto.font.ts
import localFont from 'next/font/local'
export const roboto = localFont({
src: [
{
path: './Roboto-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './Roboto-Italic.woff2',
weight: '400',
style: 'italic',
},
{
path: './Roboto-Bold.woff2',
weight: '700',
style: 'normal',
},
{
path: './Roboto-BoldItalic.woff2',
weight: '700',
style: 'italic',
},
],
variable: '--font-main',
display: 'swap',
})
```
`app/` импортирует готовый объект шрифта и только подключает его к документу:
```tsx
// src/app/layout.tsx
import type { ReactNode } from 'react'
import { roboto } from 'shared/fonts/roboto/roboto.font'
import 'shared/styles/global.css'
type RootLayoutProps = {
children: ReactNode
}
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="ru" className={roboto.variable}>
<body>{children}</body>
</html>
)
}
```
Путь в `localFont` указывается относительно `.font.ts`, поэтому файлы шрифта импортируются коротко: `./Roboto-Regular.woff2`.
Если шрифтов несколько, у каждого своя папка и свой `.font.ts`.
## Правила
- Использовать `next/font/google` или `next/font/local`.
- Не подключать шрифты через ручные `<link>` и `@font-face` без необходимости.
- Подключать шрифты один раз — в корневом layout через готовый объект шрифта.
- Использовать CSS-переменные `variable`, а не жёстко прописывать семейство в каждом компоненте.
- Локальные файлы шрифтов хранить в `src/shared/fonts/{font-name}/` рядом с `{font-name}.font.ts`.
- Не объявлять `localFont` внутри `src/app/layout.tsx`; layout только импортирует готовый шрифт.

View File

@@ -0,0 +1,95 @@
---
title: Изображения
description: Как подключать изображения через Next.js Image в проекте.
---
# Изображения
Как подключать изображения через Next.js Image в проекте.
## Назначение
Изображения рендерятся через компонент `Image` из `next/image`. Это сохраняет единый API для размеров, `alt`, lazy-loading и `priority`, даже если оптимизация изображений отключена.
В проекте оптимизация Next.js Image отключается через `unoptimized`, чтобы сборка и рантайм не зависели от встроенного image optimizer.
## Настройка
Отключение оптимизации задаётся глобально в `next.config.ts`:
```ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
images: {
unoptimized: true,
},
}
export default nextConfig
```
После этого `unoptimized` не нужно повторять на каждом `Image`.
## Использование
Статические изображения, доступные по URL, размещаются в `public/`:
```text
public/
└── images/
└── user-avatar.png
```
```tsx
import Image from 'next/image'
export const UserAvatar = () => {
return (
<Image
src="/images/user-avatar.png"
alt="Аватар пользователя"
width={96}
height={96}
/>
)
}
```
## Правила
- Использовать `Image` из `next/image`, не обычный `<img>`.
- Для контентных изображений всегда писать осмысленный `alt`.
- Для декоративных изображений использовать `alt=""`.
- Указывать `width` и `height`, если изображение не использует `fill`.
- При `fill` задавать `sizes` и контролировать размеры родителя стилями.
- `priority` ставить только для изображений первого экрана.
- SVG-иконки не оформлять как изображения — для них используется раздел [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-intro).
## Пример с `fill`
```tsx
import Image from 'next/image'
import styles from '../styles/article-card-cover.module.css'
export const ArticleCardCover = () => {
return (
<div className={styles.root}>
<Image
src="/images/article-cover.jpg"
alt="Обложка статьи"
fill
sizes="(min-width: 768px) 33vw, 100vw"
/>
</div>
)
}
```
```css
.root {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
}
```

View File

@@ -0,0 +1,81 @@
---
title: Локализация
description: Как организовать локализацию как infrastructure-модуль.
---
# Локализация
Как организовать локализацию как infrastructure-модуль.
## Назначение
Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов.
Код локализации живёт в `src/infrastructure/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infrastructure-модуля.
## Структура
```text
src/infrastructure/i18n/
├── config/
│ └── i18n.config.ts
├── dictionaries/
│ ├── ru.ts
│ └── en.ts
├── hooks/
│ └── use-translation.hook.ts
├── providers/
│ └── i18n-provider.tsx
├── types/
│ └── i18n.type.ts
└── index.ts
```
Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infrastructure/i18n`.
## Подключение
`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infrastructure/i18n/`.
```tsx
// src/app/layout.tsx
import type { ReactNode } from 'react'
import { I18nProvider } from 'infrastructure/i18n'
type RootLayoutProps = {
children: ReactNode
}
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="ru">
<body>
<I18nProvider locale="ru">{children}</I18nProvider>
</body>
</html>
)
}
```
## Использование
Компоненты получают переводы через готовый API модуля локализации:
```tsx
import { useTranslation } from 'infrastructure/i18n'
export const ProfileTitle = () => {
const { t } = useTranslation()
return <h1>{t('profile.title')}</h1>
}
```
## Правила
- Локализация живёт в `infrastructure/i18n/`.
- `app/` только подключает готовый provider и передаёт locale.
- Словари не импортируются напрямую в компоненты, screens или business-модули.
- Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск.
- Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться.
- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infrastructure-модуля.

156
docs/docs/applied/module.md Normal file
View File

@@ -0,0 +1,156 @@
---
title: Модуль
description: Как должен выглядеть сгенерированный SLM-модуль в проекте.
---
# Модуль
Как должен выглядеть сгенерированный SLM-модуль в проекте.
## Назначение
Архитектурное определение модуля описано в разделе [Архитектура → Модули](/docs/basics/architecture/modules). Список сегментов описан в разделе [Архитектура → Сегменты](/docs/basics/architecture/segments).
Эта страница показывает прикладное оформление трёх типов модулей: UI, бизнес и инфраструктурный.
## Создание
1. Проверьте, что в проекте есть нужный шаблон в `.templates/`.
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](/docs/applied/templates/templates-create).
3. Сгенерируйте модуль через [VS Code или CLI](/docs/applied/templates/templates-usage).
## Типы модулей
Архитектура определяет три типа модулей ([Типы модулей](/docs/basics/architecture/modules#типы-модулей)):
| Тип | Обязательный файл | Описание |
|---|---|---|
| UI-модуль | `{name}.tsx` | Модуль, выросший из компонента |
| Бизнес-модуль | `{name}.factory.ts` | Модуль вокруг публичного runtime API |
| Инфраструктурный модуль | нет | Модуль вокруг технического сервиса |
## UI-модуль
UI-модуль — это компонент, который перерос ограничения компонента: получил собственные хуки, вложенные модули в `parts/`, сценарную логику или публичный API. Внутренняя структура та же, что у компонента: корневой `.tsx`, типы, стили, `ui/`. Но без ограничений компонента.
Подробное оформление компонентов внутри `ui/` описано в разделе [Компонент](/docs/applied/component).
## Бизнес-модуль
Бизнес-модуль строится вокруг публичного runtime API. Ключевой файл — фабрика (`{name}.factory.ts`), которая возвращает всё, что нужно внешнему коду в runtime.
Архитектурное описание фабрики: [Архитектура → Фабрика](/docs/basics/architecture/modules#фабрика).
### Структура
```text
business/customer/
├── customer.factory.ts
├── index.ts
└── types/
├── customer.type.ts
├── customer-api.type.ts
├── customer-deps.type.ts
└── customer-factory.type.ts
```
### Типы
`business/customer/types/customer-api.type.ts`
```ts
export type CustomerApi = {
useCustomer: () => Customer
CustomerCard: (props: CustomerCardProps) => ReactNode
}
```
`business/order/types/order-deps.type.ts`
```ts
export type OrderDeps = {
customer: Pick<CustomerApi, 'useCustomer'>
}
```
`business/order/types/order-factory.type.ts`
```ts
export type OrderFactory = (deps: OrderDeps) => OrderApi
```
### Фабрика без зависимостей
`business/customer/customer.factory.ts`
```ts
import type { CustomerFactory } from './types/customer-factory.type'
export const customerFactory: CustomerFactory = () => {
return {
useCustomer,
CustomerCard,
}
}
```
### Фабрика с зависимостями
`business/order/order.factory.ts`
```ts
import type { OrderFactory } from './types/order-factory.type'
export const orderFactory: OrderFactory = (deps) => {
return {
useOrder,
OrderCard,
}
}
```
### Композиция на уровне screen
```tsx
// screens/home/home.screen.tsx
import { customerFactory } from '@/business/customer'
import { orderFactory } from '@/business/order'
const customer = customerFactory()
const order = orderFactory({ customer })
const { useOrder, OrderCard } = order
export const HomeScreen = () => {
const currentOrder = useOrder()
return <OrderCard order={currentOrder} />
}
```
## Инфраструктурный модуль
Инфраструктурный модуль строится вокруг технического сервиса или интеграции. Его структура определяется природой сервиса — фиксированного корневого файла нет.
Архитектурное описание: [Архитектура → Типы модулей → Инфраструктурный модуль](/docs/basics/architecture/modules#инфраструктурный-модуль).
Пример модуля темы:
```text
theme/
├── index.ts
├── config/
├── hooks/
├── styles/
└── ui/
```
Пример модуля API-клиента:
```text
backend-api/
├── backend-api.client.ts
├── config/
├── types/
└── index.ts
```

View File

@@ -0,0 +1,186 @@
---
title: Файлы роутинга
description: Как работать со страницами и другими файлами роутинга Next.js App Router.
---
# Файлы роутинга
Как работать со страницами и другими файлами роутинга Next.js App Router.
## Назначение
`src/app/**` — точка входа приложения и слой файлового роутинга Next.js.
Файлы роутинга не реализуют интерфейс. Они описывают маршрут: читают параметры, получают данные первого рендера, подготавливают кеш или состояние и передают результат в screen.
Границы слоя описаны в [Архитектура → Слои → App](/docs/basics/architecture/layers#слой-app).
## Граница ответственности
| Область | Где живёт |
|---|---|
| Файлы маршрутов (`page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`) | `src/app/**` |
| Параметры маршрута, `metadata`, `redirect()`, `notFound()` | `src/app/**` |
| Серверные запросы для первого рендера | `src/app/**`, через готовые клиенты и сервисы нижних слоёв |
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв |
| UI страницы | `screens/` |
| Каркас страницы: header, footer, sidebar | `layouts/` |
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infrastructure/`, `shared/`) |
| CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` |
## Что можно делать в `page.tsx`
- Экспортировать `metadata` или `generateMetadata`.
- Читать `params` и `searchParams`.
- Нормализовать и валидировать параметры маршрута.
- Делать серверные запросы для первого рендера через готовые клиенты или сервисы.
- Вызывать `redirect()` и `notFound()`.
- Готовить начальные данные для screen.
- Готовить SWR `fallback` и передавать его в готовый провайдер.
- Подключать готовый провайдер стора страницы и передавать начальное состояние.
- Рендерить screen или композицию из готовых обёрток и screen.
## Что запрещено
- Писать UI-разметку страницы прямо в файле роутинга.
- Создавать локальные компоненты внутри `src/app/**`.
- Добавлять CSS Modules, стили компонентов, `components/`, `styles/`, `hooks/`, `stores/`, `services/` внутри `src/app/**`.
- Реализовывать провайдеры, сторы, хуки, API-клиенты или сервисы в файлах роутинга.
- Размещать бизнес-логику, мапперы и правила предметной области в файлах роутинга.
- Вызывать `useSWR` и доменные клиентские хуки в файлах роутинга.
## Страницы
Страница объявляется через `export default function`. Для серверных запросов используется `async function`.
```tsx
import type { Metadata } from 'next'
import { ProfileScreen } from 'screens/profile'
export const metadata: Metadata = {
title: 'Профиль',
description: 'Страница профиля пользователя',
}
type ProfilePageProps = {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return <ProfileScreen id={id} />
}
```
## Данные первого рендера
Если данные нужны до первого рендера, `page.tsx` получает их на сервере и передаёт в screen. Сам запрос выполняется через готовый клиент или сервис нижнего слоя.
```tsx
import { notFound } from 'next/navigation'
import { userApi } from 'infrastructure/backend-api'
import { UserScreen } from 'screens/user'
type UserPageProps = {
params: Promise<{ id: string }>
}
export default async function UserPage({ params }: UserPageProps) {
const { id } = await params
const user = await userApi.users.get(id)
if (!user) {
notFound()
}
return <UserScreen user={user} />
}
```
Если данные нужны нескольким клиентским SWR-хукам, файл роутинга может обернуть дерево в `SWRConfig` и передать `fallback`. Запросы стартуют на сервере, а клиентские хуки получают данные из кеша.
Ключи `fallback` должны совпадать с ключами внутри GET-хуков REST-клиента. Для array-key используется `unstable_serialize`.
```tsx
import type { ReactNode } from 'react'
import { SWRConfig, unstable_serialize } from 'swr'
import {
backendApi,
getCurrentUserKey,
getPostListKey,
} from 'infrastructure/backend-api'
type FeedLayoutProps = {
children: ReactNode
}
export default async function FeedLayout({ children }: FeedLayoutProps) {
const userPromise = backendApi.user.getCurrent()
const postsPromise = backendApi.posts.list()
return (
<SWRConfig
value={{
fallback: {
[unstable_serialize(getCurrentUserKey())]: userPromise,
[unstable_serialize(getPostListKey())]: postsPromise,
},
}}
>
{children}
</SWRConfig>
)
}
```
Подробнее о стратегиях запросов и начальных данных для клиентских хуков: [REST → Стратегии получения данных](/docs/data/rest/strategies/), [REST → Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
## Инициализация состояния
Файл роутинга может подключить готовый провайдер стора страницы, если состояние зависит от маршрута или данных первого рендера. Реализация стора и провайдера не размещается в `src/app/**`.
```tsx
import { ProfileScreen, ProfileStoreProvider } from 'screens/profile'
type ProfilePageProps = {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return (
<ProfileStoreProvider initialState={{ userId: id }}>
<ProfileScreen />
</ProfileStoreProvider>
)
}
```
## Layout
`layout.tsx` подключает готовую инициализацию приложения: глобальные стили, провайдеры и верхнеуровневые обёртки из нижних слоёв.
Вёрстка layout-каркаса выносится в слой `layouts/`. Реализация провайдеров, стилей и UI не размещается в `app/`.
## Error и Not Found
`error.tsx` и `not-found.tsx` делегируют разметку готовым screen или widget. В файле роутинга остаётся только адаптация API Next.js к пропсам нижнего слоя.
```tsx
'use client'
import { ErrorScreen } from 'screens/error'
type ErrorPageProps = {
error: Error & { digest?: string }
reset: () => void
}
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
return <ErrorScreen error={error} reset={reset} />
}
export default ErrorPage
```

View File

@@ -67,4 +67,4 @@ export default {
Опция `importFrom` у `postcss-custom-media` удалена в v10+; её роль теперь выполняет `@csstools/postcss-global-data`. Опция `importFrom` у `postcss-custom-media` удалена в v10+; её роль теперь выполняет `@csstools/postcss-global-data`.
Импортировать `media.css` в файлы компонентов **не нужно** и запрещено правилами (см. [Использование](/docs/usage/styles), раздел «Импорт стилей»). Импортировать `media.css` в файлы компонентов **не нужно** и запрещено правилами (см. [Использование стилей](/docs/applied/styles/styles-usage), раздел «Импорт стилей»).

View File

@@ -41,7 +41,7 @@ public/
```text ```text
src/ src/
├── app/ # Роутинг Next.js, провайдеры, глобальные стили ├── app/ # Роутинг Next.js и точка входа приложения
├── layouts/ # Каркасы страниц (header, footer, sidebar) ├── layouts/ # Каркасы страниц (header, footer, sidebar)
├── screens/ # Контент конкретной страницы ├── screens/ # Контент конкретной страницы
├── widgets/ # Составные блоки интерфейса, не привязанные к домену ├── widgets/ # Составные блоки интерфейса, не привязанные к домену
@@ -55,12 +55,13 @@ src/
### Папка `app/` ### Папка `app/`
Точка входа приложения: инициализация (провайдеры, глобальные стили) и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты). Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
`app/` подключает готовую инициализацию из нижних слоёв, но не реализует провайдеры, стили, UI-компоненты, хуки, сторы или сервисы.
Подробнее о границах слоя: [Архитектура → Слои → App](/docs/basics/architecture/layers#слой-app).
```text ```text
src/app/ src/app/
├── providers/ # Провайдеры приложения
├── styles/ # Глобальные стили
├── layout.tsx # Корневой layout ├── layout.tsx # Корневой layout
└── page.tsx # Главная страница └── page.tsx # Главная страница
``` ```
@@ -79,7 +80,7 @@ src/app/
└── store/ # Шаблон стора └── store/ # Шаблон стора
``` ```
Подробнее о генерации описано в разделе [Шаблоны и генерация кода](./templates-generation). Подробнее о генерации описано в разделе [Шаблоны генерации](/docs/applied/templates/templates-intro).
## Конфигурационные файлы ## Конфигурационные файлы

View File

@@ -1,17 +1,16 @@
--- ---
title: Стили title: Настройка стилей
description: "Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили." description: "Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили."
keywords: [variables.css, media.css, global.css, shared/styles, токены, переменные, custom-media, breakpoints, подключение стилей, базовые стили, инициализация] keywords: [variables.css, media.css, global.css, shared/styles, токены, переменные, custom-media, breakpoints, подключение стилей, базовые стили, инициализация]
--- ---
# Стили # Настройка стилей
Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили. Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
## Требования ## Требования
- Структура `src/` соответствует SLM ([Структура проекта](/docs/usage/project-structure)). Глобальные стили живут в `src/shared/styles/`, не в `src/app/`. - Установлен PostCSS или любой другой pre/post-процессор с поддержкой `@custom-media`.
- В проекте нет `globals.css` с кастомным содержимым и не установлен `tailwindcss`.
## Файлы ## Файлы
@@ -27,7 +26,7 @@ keywords: [variables.css, media.css, global.css, shared/styles, токены, п
- В приложение импортируется **только** `global.css`. - В приложение импортируется **только** `global.css`.
- `variables.css` и будущие глобальные файлы (резеты, темы, типографика) подключаются в `global.css` через `@import`. - `variables.css` и будущие глобальные файлы (резеты, темы, типографика) подключаются в `global.css` через `@import`.
- `media.css` **не импортируется** — ни в `global.css`, ни в компоненты, ни в точку инициализации. Его читает CSS-процессор на этапе сборки (см. [PostCSS](/docs/setup/postcss)). - `media.css` **не импортируется** — ни в `global.css`, ни в компоненты, ни в точку инициализации. Его читает CSS-процессор на этапе сборки (см. [PostCSS](/docs/applied/postcss)).
## Корневой `font-size` ## Корневой `font-size`
@@ -172,6 +171,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
## Дальше ## Дальше
- [PostCSS](/docs/setup/postcss) — подключить процессор, чтобы заработали `@media (--md)` и вложенность. - [PostCSS](/docs/applied/postcss) — подключить процессор, чтобы заработали `@media (--md)` и вложенность.
- [Стили: использование](/docs/usage/styles) — правила написания CSS в компонентах. - [Использование стилей](/docs/applied/styles/styles-usage) — правила написания CSS в компонентах.
- [SVG-спрайты](/docs/setup/svg-sprites) — стили иконок отдельно от глобальных. - [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup) — стили иконок отдельно от глобальных.

View File

@@ -1,9 +1,9 @@
--- ---
title: Стили title: Использование стилей
description: Как пишутся стили в проекте. description: Как пишутся стили в проекте.
--- ---
# Стили # Использование стилей
Как пишутся стили в проекте. Как пишутся стили в проекте.
@@ -12,6 +12,7 @@ description: Как пишутся стили в проекте.
- Только **PostCSS** и **CSS Modules** для кастомной стилизации. - Только **PostCSS** и **CSS Modules** для кастомной стилизации.
- Подход **Mobile First** — стили пишутся от мобильных к десктопу. - Подход **Mobile First** — стили пишутся от мобильных к десктопу.
- Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`). - Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`).
- Корневой класс каждого CSS Module компонента всегда называется `.root` — это упрощает ориентацию в DevTools и отладку DOM.
- Модификаторы — отдельный класс с `_`, применяется через `&._modifier`. - Модификаторы — отдельный класс с `_`, применяется через `&._modifier`.
**Хорошо** **Хорошо**
@@ -143,13 +144,13 @@ description: Как пишутся стили в проекте.
## CSS-переменные ## CSS-переменные
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `app/styles/variables.css` через `:root`. - Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `src/shared/styles/variables.css` через `:root`.
- Файл переменных подключается один раз в корневом layout/entry point — после этого переменные доступны глобально через каскад. - Файл переменных подключается через `src/shared/styles/global.css`, который импортируется один раз в `src/app/layout.tsx`.
- Не дублировать магические значения в компонентах. - Не дублировать магические значения в компонентах.
**Хорошо** **Хорошо**
```css ```css
/* app/styles/variables.css */ /* src/shared/styles/variables.css */
:root { :root {
--color-primary: #3b82f6; --color-primary: #3b82f6;
--color-bg: #ffffff; --color-bg: #ffffff;
@@ -183,11 +184,11 @@ description: Как пишутся стили в проекте.
## Custom Media ## Custom Media
- Breakpoints определяются через Custom Media Queries в `app/styles/media.css`. - Breakpoints определяются через Custom Media Queries в `src/shared/styles/media.css`.
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей. - Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
```css ```css
/* app/styles/media.css */ /* src/shared/styles/media.css */
@custom-media --sm (min-width: 36em); @custom-media --sm (min-width: 36em);
@custom-media --md (min-width: 62em); @custom-media --md (min-width: 62em);
@custom-media --lg (min-width: 82em); @custom-media --lg (min-width: 82em);

View File

@@ -0,0 +1,31 @@
---
title: SVG-спрайты
description: "Что такое SVG-спрайты и какие проблемы они решают."
---
# SVG-спрайты
Что такое SVG-спрайты и какие проблемы они решают.
## Проблема
Иконки в проекте — это десятки и сотни SVG-файлов, которые нужно как-то доставлять в интерфейс. Подход «один `<img>` на иконку» или инлайн SVG в каждом компоненте приводят к трём проблемам:
- **Дублирование.** Инлайн SVG в нескольких компонентах — один и тот же код размазан по проекту. Изменение иконки требует правок в десяти местах.
- **Размер бандла.** Каждый инлайн SVG — полный XML-код, который попадает в JS-бандл. Сотня иконок × средний размер SVG = сотни килобайт, которые браузер парсит как JavaScript, а не как статику.
- **Нет управления цветом.** Инлайн SVG жёстко закрашивает иконку. Сменить цвет по состоянию (`:hover`, `._disabled`) — значит дублировать SVG или городить `currentColor`-хаки в каждом компоненте.
## Решение
SVG-спрайты — это единый файл-контейнер, в который собираются все иконки проекта. В коде используется один React-компонент `<SvgSprite icon="name"/>`, а браузер загружает спрайт как статику — один раз, с кешированием.
Что дают SVG-спрайты:
- **Один источник.** Каждая иконка — один SVG-файл в `src/shared/sprites/`. Обновил файл — иконка обновилась везде.
- **Лёгкий бандл.** Спрайт отдаётся как статический файл из `public/`, не попадает в JavaScript. Типы имён иконок генерируются автоматически — автодополнение работает без ручных описаний.
- **Цвет через CSS.** При сборке цвета в SVG заменяются на CSS-переменные. Цвет иконки меняется через `color` родителя или через переменные `--icon-color-N` — как любой другой стиль.
## Состав раздела
- [Настройка](/docs/applied/svg-sprites/svg-sprites-setup) — подключение пакета, конфигурация, первая генерация.
- [Использование](/docs/applied/svg-sprites/svg-sprites-usage) — добавление иконок, компонент `<SvgSprite/>`, управление цветом.

View File

@@ -1,18 +1,12 @@
--- ---
title: SVG-спрайты title: Настройка SVG-спрайтов
description: Подключение SVG-спрайтов в новом проекте. description: Подключение SVG-спрайтов в новом проекте.
keywords: [svg-sprites, установка, настройка, config, пакет, "@gromlab/svg-sprites", svg-sprites.config.ts] keywords: [svg-sprites, установка, настройка, config, пакет, "@gromlab/svg-sprites", svg-sprites.config.ts]
--- ---
# SVG-спрайты # Настройка SVG-спрайтов
Подключение SVG-спрайтов в новом проекте. Подключение SVG-спрайтов в новом проекте.
## Требования
- Node.js 18+
- React 18+
## Установка ## Установка
1. Установить пакет: 1. Установить пакет:
@@ -21,7 +15,7 @@ keywords: [svg-sprites, установка, настройка, config, паке
npm install @gromlab/svg-sprites npm install @gromlab/svg-sprites
``` ```
2. Создать `svg-sprites.config.ts` в корне проекта (см. «Стандартный конфиг»). 2. Создать `svg-sprites.config.ts` в корне проекта (см. [Стандартный конфиг](#стандартныи-конфиг)).
3. Создать папку входа для SVG-файлов в слое `shared`: 3. Создать папку входа для SVG-файлов в слое `shared`:
@@ -29,7 +23,7 @@ keywords: [svg-sprites, установка, настройка, config, паке
mkdir -p src/shared/sprites/icons mkdir -p src/shared/sprites/icons
``` ```
Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](/docs/usage/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` посторонних каталогов вне слоёв не заводим. Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` посторонних каталогов вне слоёв не заводим.
4. Добавить скрипты в `package.json`: 4. Добавить скрипты в `package.json`:
@@ -53,11 +47,38 @@ keywords: [svg-sprites, установка, настройка, config, паке
/src/ui/svg-sprite/ /src/ui/svg-sprite/
``` ```
6. Выполнить первую генерацию: 6. Выполнить первую генерацию:
```bash ```bash
npm run sprite npm run sprite
``` ```
7. Подключить спрайт в layout. Глобальный спрайт (иконки) подключается через `<link rel="preload">` в корневом layout — браузер загрузит файл заранее и закеширует:
```tsx
// src/app/layout.tsx
import 'shared/styles/global.css'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'App',
description: '',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ru">
<head>
<link rel="preload" href="/sprites/icons.sprite.stack.svg" as="image" />
</head>
<body>{children}</body>
</html>
)
}
```
Локальные спрайты (если есть) подключаются аналогично в layout конкретной страницы или маршрута.
## Стандартный конфиг ## Стандартный конфиг
@@ -105,3 +126,7 @@ transform: {
### Режим ### Режим
По умолчанию `mode: 'stack'` — не указывать явно. Переход на `symbol` требует обоснования: превью и примеры в пакете оптимизированы под `stack`. По умолчанию `mode: 'stack'` — не указывать явно. Переход на `symbol` требует обоснования: превью и примеры в пакете оптимизированы под `stack`.
## Дальше
- [Использование](/docs/applied/svg-sprites/svg-sprites-usage) — добавление иконок, компонент `<SvgSprite/>`, управление цветом.

View File

@@ -1,10 +1,10 @@
--- ---
title: SVG-спрайты title: Использование SVG-спрайтов
description: Как добавлять и использовать SVG-иконки в коде. description: Как добавлять и использовать SVG-иконки в коде.
keywords: [svg, спрайт, sprite, иконка, icon, SvgSprite, превью, preview, цвет, color] keywords: [svg, спрайт, sprite, иконка, icon, SvgSprite, превью, preview, цвет, color]
--- ---
# SVG-спрайты # Использование SVG-спрайтов
Как добавлять и использовать SVG-иконки в коде. Как добавлять и использовать SVG-иконки в коде.

View File

@@ -0,0 +1,97 @@
---
title: Создание шаблонов генерации
description: "Структура шаблонов, синтаксис переменных и примеры."
keywords: [шаблоны, templates, .templates, syntax, переменные, kebabCase, pascalCase, scaffold]
---
<!-- @formatter:off -->
::: v-pre
# Создание шаблонов генерации
Структура шаблонов, синтаксис переменных и примеры.
## Структура шаблонов
Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон.
```text
.templates/
├── component/ # шаблон компонента
│ └── {{name.kebabCase}}/
│ ├── styles/
│ │ └── {{name.kebabCase}}.module.css
│ ├── types/
│ │ └── {{name.kebabCase}}-props.type.ts
│ ├── {{name.kebabCase}}.tsx
│ └── index.ts
└── store/ # шаблон Zustand стора
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.store.ts
├── {{name.kebabCase}}.type.ts
└── index.ts
```
## Обязательный шаблон компонента
Перед созданием компонентов в проекте должен существовать шаблон `.templates/component`.
Если шаблона нет, компонент не создаётся вручную. Сначала создаётся шаблон компонента, затем компонент генерируется через [VS Code или CLI](/docs/applied/templates/templates-usage).
## Синтаксис шаблонов
### Переменные
Переменные работают в именах файлов/папок и внутри файлов:
```text
{{variable}}
```
Переменные могут быть любыми. `name` — дефолтная, подставляется генератором автоматически. Если реализация требует дополнительных параметров — можно использовать произвольные наборы переменных.
### Модификаторы
Модификаторы меняют регистр и формат записи переменной:
```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'
```
## Дальше
- [Использование](/docs/applied/templates/templates-usage) — генерация через VS Code плагин и CLI.
:::

View File

@@ -0,0 +1,32 @@
---
title: Шаблоны генерации
description: "Что такое шаблоны кодогенерации и какие проблемы они решают."
---
# Шаблоны генерации
Что такое шаблоны кодогенерации и какие проблемы они решают.
## Проблема
Каждый новый модуль в проекте — компонент, стор, бизнес-модуль — требует однотипной структуры файлов и boilerplate-кода. Ручное создание приводит к трём проблемам:
- **Расхождения.** Разные разработчики создают модули по-разному: забывают `index.ts`, называют типы не по канону, пропускают стили.
- **Время.** Создание одного компонента с типами, стилями и экспортом — 510 минут рутины. За спринт набегают часы.
- **Ошибки копипасты.** Копирование существующего модуля и переименование — источник опечаток и забытых ссылок.
## Решение
Шаблоны кодогенерации — это папки с файлами-заготовками в `.templates/`. Вместо ручного создания файлов разработчик вызывает генератор, указывает имя — и получает готовый модуль со всей структурой, именами и boilerplate, подставленными автоматически.
Что дают шаблоны:
- **Единообразие.** Все модули одного типа идентичны по структуре. Канон живёт в шаблоне, а не в памяти разработчика.
- **Скорость.** Генерация модуля — одна команда. Остальное время — на бизнес-логику.
- **Согласованность с архитектурой.** Шаблоны учитывают SLM: правильные слои, сегменты, экспорты. Отклонение от стайлгайда требует осознанного усилия, а не случайного упущения.
## Состав раздела
- [Настройка](/docs/applied/templates/templates-setup) — первичная установка: скачивание стандартного набора шаблонов в проект.
- [Создание шаблонов](/docs/applied/templates/templates-create) — структура файлов, синтаксис переменных, примеры.
- [Использование](/docs/applied/templates/templates-usage) — генерация через VS Code плагин и CLI.

View File

@@ -0,0 +1,44 @@
---
title: Настройка шаблонов генерации
description: Первичная установка шаблонов кодогенерации в проект.
keywords: [шаблоны, templates, .templates, tiged, generator, генератор шаблонов, скачать шаблоны, scaffold]
---
# Настройка шаблонов генерации
Первичная установка шаблонов кодогенерации в проект.
## Установка
1. Проверить, что `.templates/` отсутствует (или согласовать перезапись, если папка уже есть).
2. Скачать папку из эталонного репозитория:
```bash
npx tiged git@gromlab.ru:templates/nextjs-template.git/.templates .templates
```
3. Если `tiged` падает в default-режиме (HTTP-tarball у Gitea) — повторить с явным git-режимом:
```bash
npx tiged --mode=git git@gromlab.ru:templates/nextjs-template.git/.templates .templates
```
4. Проверить генерацию:
```bash
npx @gromlab/create component test src/ui
```
После проверки — удалить тестовый модуль.
## Проверка установки
- В корне проекта есть папка `.templates/`.
- Внутри `.templates/` присутствуют стандартные шаблоны (или согласованный кастомный набор).
- Пробная генерация через `npx @gromlab/create ...` отрабатывает без ошибок.
## Дальше
- [Создание шаблонов](/docs/applied/templates/templates-create) — структура файлов, синтаксис переменных, примеры.
- [Использование](/docs/applied/templates/templates-usage) — генерация через VS Code плагин и CLI.

View File

@@ -0,0 +1,45 @@
---
title: Использование шаблонов генерации
description: Генерация файлов из шаблонов через VS Code плагин и CLI.
keywords: [шаблоны, templates, generate, VS Code, CLI, gromlab/create, npx, scaffold]
---
# Использование шаблонов генерации
Генерация файлов из шаблонов через VS Code плагин и CLI.
::: danger Ручное создание запрещено
Файлы, для которых есть шаблоны в `.templates/`, создаются только генератором. Ручное создание компонента, модуля, стора или другого шаблонного блока запрещено.
Если нужного шаблона нет, сначала создайте шаблон в `.templates/`, затем сгенерируйте код на его основе.
:::
## Через VS Code
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
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` | Компонент в текущей папке |
| `npx @gromlab/create module auth src/business` | Бизнес-модуль |
| `npx @gromlab/create widget header src/widgets` | Виджет |
| `npx @gromlab/create layout admin src/layouts` | Layout |
| `npx @gromlab/create store auth src/business/auth/stores` | Стор |
CLI вызывается через `npx`, в `package.json` отдельно не добавляется.

View File

@@ -1,11 +1,19 @@
---
title: SLM Design
description: "Архитектурный подход проекта: что такое SLM и как он устроен."
---
# SLM Design # SLM Design
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
Архитектурный подход проекта: что такое SLM и как он устроен. ::: warning Локальная копия
Документация по архитектуре — локальная копия. Оригинал находится на сайте [slm-design.gromlab.ru](https://slm-design.gromlab.ru/).
:::
## Разделы спецификации
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
## Преимущества ## Преимущества
@@ -19,7 +27,7 @@ Cross-domain зависимости в бизнес-слое реализуют
### Разделение ответственности без перегрузки слоёв ### Разделение ответственности без перегрузки слоёв
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода. Сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
### Горизонтальная инкапсуляция ### Горизонтальная инкапсуляция
@@ -73,7 +81,7 @@ src/
│ ├── orders/ │ ├── orders/
│ └── chat/ │ └── chat/
├── infrastructure/ ├── infra/
│ ├── theme/ │ ├── theme/
│ ├── i18n/ │ ├── i18n/
│ ├── backend-api/ │ ├── backend-api/

View File

@@ -1,11 +1,6 @@
--- # Слои
title: Слои SLM
description: Из каких слоёв состоит SLM-архитектура и как они связаны.
---
# Слои SLM Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
Из каких слоёв состоит SLM-архитектура и как они связаны.
## Определение ## Определение
@@ -18,7 +13,7 @@ description: Из каких слоёв состоит SLM-архитектур
| Группа | Слои | Описание | | Группа | Слои | Описание |
|--------|------|----------| |--------|------|----------|
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы | | Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит | | Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги | | Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
## Направление зависимостей ## Направление зависимостей
@@ -26,12 +21,12 @@ description: Из каких слоёв состоит SLM-архитектур
Любой импорт между модулями — только через публичный API. Любой импорт между модулями — только через публичный API.
``` ```
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared app → [ layouts | screens ] → widgets → business → infra → ui → shared
``` ```
- `layouts` и `screens` — параллельные слои, не импортируют друг друга - `layouts` и `screens` — параллельные слои, не импортируют друг друга
- Модули одного слоя в группе «Композиция» изолированы друг от друга - Модули одного слоя в группе «Композиция» изолированы друг от друга
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API - Модули одного слоя `infra` и `ui` могут импортировать друг друга через публичный API
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую - Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях - Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
@@ -129,7 +124,7 @@ src/widgets/
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами. Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую. Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный runtime API через фабрику в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `import type`.
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки. Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
@@ -160,19 +155,20 @@ src/business/
- Один модуль = один бизнес-домен - Один модуль = один бизнес-домен
- Циклические зависимости между доменами запрещены - Циклические зависимости между доменами запрещены
- Импорт кода между доменами — через фабрику. `import type` — напрямую - Публичный runtime API — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты
- Импорт runtime-кода между доменами — через фабрику. `import type` — напрямую
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/` - Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
## Слой Infrastructure ## Слой infra
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль. Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`.
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). Отличие от `shared/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
```text ```text
src/infrastructure/ src/infra/
├── theme/ ├── theme/
├── i18n/ ├── i18n/
├── backend-api/ ├── backend-api/
@@ -185,7 +181,7 @@ src/infrastructure/
### Требования ### Требования
- Один модуль = один техсервис - Один модуль = один техсервис
- Импортирует `infrastructure/`, `ui/`, `shared/` - Импортирует `infra/`, `ui/`, `shared/`
## Слой UI ## Слой UI
@@ -236,7 +232,7 @@ src/ui/
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует. Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги). Отличие от `infra/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь. Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
@@ -250,4 +246,4 @@ src/shared/
### Требования ### Требования
- Не имеет runtime-состояния - Не имеет runtime-состояния

View File

@@ -0,0 +1,284 @@
# Модули
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
## Определение
**Модуль — минимальная архитектурная единица SLM. Он живёт на одном из слоёв, владеет конкретной областью ответственности и предоставляет наружу только публичный API.**
Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно.
Модуль не обязан быть UI-блоком. Это может быть страница, виджет, бизнес-домен, инфраструктурный сервис или UI-kit сущность.
Главная граница модуля — не папка, а ответственность.
## Компонент
**Компонент — презентационная единица модуля, которая находится только в `ui/` своего родительского модуля и отвечает за отображение части интерфейса.**
Компонент не является архитектурной единицей: он не владеет сценарием, зависимостями, данными или внутренней структурой. Он работает только внутри границы родительского модуля.
> Компонент отображает. Модуль организует.
Компонент не может:
- Импортировать код проекта за пределами родительского модуля.
- Владеть архитектурными зависимостями.
- Содержать любые компоненты.
- Содержать любые модули.
- Делать внешние запросы.
- Самостоятельно получать данные.
- Выбирать источник данных.
- Композировать данные.
- Вызывать сценарные хуки.
- Оркестрировать сценарий.
- Композировать модули.
- Решать, как устроен процесс.
- Содержать бизнес-логику.
- Содержать сценарную логику.
Если компоненту требуется что-то из этого списка, он перестаёт быть компонентом и должен быть оформлен как модуль.
```text
auth/
├── ui/
│ └── logout-button/
│ ├── logout-button.tsx
│ ├── styles/
│ │ └── logout-button.module.css
│ ├── types/
│ │ └── logout-button-props.type.ts
│ └── index.ts
└── index.ts
```
## Что считается модулем
Модулем считается папка, которая представляет самостоятельную область ответственности и имеет публичную границу.
Примеры модулей:
- `screens/home/` — модуль страницы.
- `widgets/page-heading/` — модуль виджета.
- `business/auth/` — модуль бизнес-домена.
- `infra/theme/` — модуль инфраструктурного сервиса.
- `ui/button/` — модуль UI-kit сущности.
- `screens/home/parts/hero-section/` — вложенный модуль страницы.
Не считаются модулями:
- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/` — это сегменты.
- `screens/shop/`, `business/commerce/` — это группы, если в них нет `index.ts`.
- `screens/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента.
## Типы модулей
Тип модуля определяет обязательный корневой файл и стартовую структуру.
### UI-модуль
Модуль строится вокруг основного UI-компонента и обязан иметь основной `.tsx` файл в корне:
```text
header/
├── header.tsx
└── index.ts
```
`ui/` внутри такого модуля используется только для компонентов, которые помогают корневому `.tsx` файлу.
### Бизнес-модуль
Бизнес-модуль — модуль, который строится вокруг публичного runtime API.
Бизнес-модуль обязан иметь фабрику в корне:
```text
auth/
├── auth.factory.ts
├── index.ts
└── types/
```
Фабрика возвращает публичный runtime API модуля.
### Инфраструктурный модуль
Инфраструктурный модуль — модуль, который строится вокруг технического сервиса или интеграции.
Инфраструктурный модуль не обязан иметь фиксированный корневой файл. Его структура определяется природой сервиса.
```text
theme/
├── index.ts
├── config/
├── hooks/
├── styles/
└── ui/
```
```text
backend-api/
├── backend-api.client.ts
├── config/
├── types/
└── index.ts
```
## Структура
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль включает только те части, которые нужны его ответственности.
```text
{module-name}/
├── {module-name}.factory.ts # фабрика (для business-модулей)
├── {module-name}.tsx # корневой файл модуля (опционален)
├── ui/ # компоненты модуля
├── parts/ # вложенные модули
├── hooks/ # хуки
├── stores/ # сторы состояния
├── services/ # внешние источники данных
├── mappers/ # трансформация данных между форматами
├── types/ # типы
├── styles/ # стили
├── lib/ # утилиты модуля
├── config/ # константы и конфигурация
└── index.ts # публичный API
```
Подробное описание сегментов — в разделе [Сегменты](./segments.md).
## Публичный API
Внешний код импортирует модуль только через публичный API.
```ts
// Хорошо
import { customerFactory } from '@/business/customer'
import type { Customer } from '@/business/customer'
```
```ts
// Плохо
import { validateToken } from '@/business/auth/lib/tokens'
```
`index.ts` модуля не обязан экспортировать всё содержимое. Он экспортирует только то, что действительно нужно снаружи.
Внутренние сегменты модуля остаются деталями реализации.
Business-модуль экспортирует из `index.ts` только фабрику и type-only экспорты. Хуки, компоненты, сервисы, мапперы и утилиты напрямую из `index.ts` не экспортируются — они доступны через API, который возвращает фабрика.
```ts
// business/customer/index.ts
export { customerFactory } from './customer.factory'
export type { Customer } from './types/customer.type'
export type { CustomerApi } from './types/customer-api.type'
export type { CustomerDeps } from './types/customer-deps.type'
export type { CustomerFactory } from './types/customer-factory.type'
```
## Фабрика
Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный runtime API модуля.
Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика.
Модуль без cross-domain зависимостей экспортирует фабрику без аргументов. Модуль с зависимостями — фабрику, принимающую зависимости доменными именами. Типы всегда экспортируются напрямую через `export type``import type` не является runtime-зависимостью.
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
### Структура business-модуля
```text
business/customer/
├── customer.factory.ts
├── index.ts
└── types/
├── customer.type.ts
├── customer-api.type.ts
├── customer-deps.type.ts
└── customer-factory.type.ts
```
### Типы
```ts
// business/customer/types/customer-api.type.ts
export type CustomerApi = {
useCustomer: () => Customer
CustomerCard: (props: CustomerCardProps) => ReactNode
}
```
```ts
// business/order/types/order-deps.type.ts
export type OrderDeps = {
customer: Pick<CustomerApi, 'useCustomer'>
}
```
```ts
// business/order/types/order-factory.type.ts
export type OrderFactory = (deps: OrderDeps) => OrderApi
```
### Фабрика без зависимостей
```ts
// business/customer/customer.factory.ts
import type { CustomerFactory } from './types/customer-factory.type'
export const customerFactory: CustomerFactory = () => {
return {
useCustomer,
CustomerCard,
}
}
```
### Фабрика с зависимостями
```ts
// business/order/order.factory.ts
import type { OrderFactory } from './types/order-factory.type'
export const orderFactory: OrderFactory = (deps) => {
return {
useOrder,
OrderCard,
}
}
```
### Композиция на уровне screen
```tsx
// screens/home/home.screen.tsx
import { customerFactory } from '@/business/customer'
import { orderFactory } from '@/business/order'
const customer = customerFactory()
const order = orderFactory({ customer })
const { useOrder, OrderCard } = order
export const HomeScreen = () => {
const currentOrder = useOrder()
return <OrderCard order={currentOrder} />
}
```
## Жизненный цикл
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
- Нужен на одной странице → `screens/{name}/parts/`
- Появился в 2+ местах → поднимается по природе:
- абстрактный UI → `ui/`
- блок с данными/логикой → `widgets/`
- представление бизнес-домена → `business/{area}/parts/`
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.

View File

@@ -1,165 +0,0 @@
---
title: Модули SLM
description: Что такое модуль в SLM-архитектуре и как он устроен.
---
# Модули SLM
Что такое модуль в SLM-архитектуре и как он устроен.
## Определение
**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.**
## Модуль vs компонент
**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля.
**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`).
```text
auth/
├── ui/
│ ├── auth-guard.tsx
│ └── logout-button.tsx
├── parts/
│ ├── login-form/
│ ├── registration-form/
│ └── restore-form/
├── hooks/
├── stores/
├── types/
├── auth.tsx # корневой компонент (опционален)
└── index.ts
```
## Структура
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов.
```text
{module-name}/
├── {module-name}.tsx # корневой компонент (опционален)
├── ui/ # компоненты модуля (только .tsx)
├── parts/ # вложенные модули (со своими сегментами)
├── hooks/ # хуки
├── stores/ # сторы состояния
├── services/ # внешние источники данных
├── mappers/ # трансформация данных между форматами
├── types/ # типы
├── styles/ # стили
├── lib/ # утилиты модуля
├── config/ # константы
└── index.ts # публичный API
```
Подробное описание каждого сегмента — в разделе [Сегменты](/docs/basics/architecture/reference/segments).
## Публичный API
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
```ts
// business/auth/index.ts
export type { User, Session } from './types/user.types'
export { useAuth } from './hooks/use-auth.hook'
export { AuthGuard } from './ui/auth-guard'
```
Импорт в обход `index.ts` запрещён:
```ts
// Плохо
import { validateToken } from '@/business/auth/lib/tokens'
// Хорошо
import { useAuth } from '@/business/auth'
```
## Фабрика
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью.
### Модуль без зависимостей — прямой экспорт:
```ts
// business/auth/index.ts
export { useAuth } from './hooks/use-auth'
export { useCurrentUser } from './hooks/use-current-user'
export type { User, Session } from './types'
```
### Модуль с зависимостями — фабрика:
```ts
// business/chat/types/deps.ts
import type { User } from '@/business/auth'
export interface ChatDeps {
useCurrentUser: () => User | null
}
```
```ts
// business/chat/index.ts
import type { ChatDeps } from './types/deps'
export function chatFactory(deps: ChatDeps) {
return {
useMessages: (roomId: string) => {
const user = deps.useCurrentUser()
// ...
},
useSendMessage: (roomId: string) => {
const user = deps.useCurrentUser()
return (text: string) => { /* ... */ }
},
useChatRooms: () => {
const user = deps.useCurrentUser()
// ...
},
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
}
}
export type { Message, ChatRoom } from './types'
export type { ChatDeps } from './types/deps'
```
### Использование на странице:
```tsx
// screens/support/support.tsx
import { useCurrentUser } from '@/business/auth'
import { chatFactory } from '@/business/chat'
const chat = chatFactory({ useCurrentUser })
export function SupportScreen() {
const { useMessages, useSendMessage, ChatBadge } = chat
const messages = useMessages('support')
const sendMessage = useSendMessage('support')
return (
<div>
<ChatBadge count={messages.length} />
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
<MessageInput onSend={sendMessage} />
</div>
)
}
```
## Жизненный цикл
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
- Нужен на одной странице → `screens/{name}/parts/`
- Появился в 2+ местах → поднимается по природе:
- абстрактный UI → `ui/`
- блок с данными/логикой → `widgets/`
- представление бизнес-домена → `business/{area}/parts/`
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.

View File

@@ -1,11 +1,6 @@
--- # Сегменты
title: Сегменты SLM
description: Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
---
# Сегменты SLM Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
## Определение ## Определение
@@ -15,7 +10,7 @@ description: Что такое сегмент модуля в SLM-архитек
| Сегмент | Содержимое | | Сегмент | Содержимое |
|---------|------------| |---------|------------|
| `ui/` | Компоненты модуля — только `.tsx` файлы | | `ui/` | Презентационные компоненты родительского модуля |
| `parts/` | Вложенные модули со своими сегментами | | `parts/` | Вложенные модули со своими сегментами |
| `hooks/` | React-хуки | | `hooks/` | React-хуки |
| `stores/` | Сторы состояния | | `stores/` | Сторы состояния |
@@ -28,24 +23,48 @@ description: Что такое сегмент модуля в SLM-архитек
## Сегмент ui/ ## Сегмент ui/
Компоненты, принадлежащие модулю. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля. Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля.
Компонент в `ui/`:
- Находится в собственной папке.
- Может содержать только `{name}.tsx`, `index.ts`, `styles/`, `types/`.
- Не содержит любые компоненты.
- Не содержит любые модули.
- Не импортирует код проекта за пределами родительского модуля.
- Не делает внешние запросы.
- Не вызывает сценарные хуки.
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
- Не содержит бизнес-логику или сценарную логику.
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент).
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
```text ```text
auth/ user/
├── ui/ ├── ui/
│ ├── auth-provider.tsx │ ├── user-avatar/
│ ├── auth-guard.tsx │ ├── user-avatar.tsx
└── logout-button.tsx │ ├── styles/
│ │ │ └── user-avatar.module.css
│ │ ├── types/
│ │ │ └── user-avatar-props.type.ts
│ │ └── index.ts
│ └── user-status/
│ ├── user-status.tsx
│ └── index.ts
├── types/ ├── types/
├── hooks/ ├── hooks/
├── user.tsx
└── index.ts └── index.ts
``` ```
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`. Если UI-сущности нужна внутренняя декомпозиция, сценарная логика, получение данных или собственные архитектурные зависимости — это уже не компонент в `ui/`, а модуль в `parts/`.
## Сегмент parts/ ## Сегмент parts/
Вложенные модули со своими сегментами. Каждый элемент `parts/` — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д. Вложенные модули со своими сегментами. `parts/` содержит только модули: каждый элемент `parts/`папка полноценного модуля с собственным публичным API. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не размещаются.
```text ```text
home/ home/
@@ -53,17 +72,20 @@ home/
│ ├── hero-section/ │ ├── hero-section/
│ │ ├── hero-section.tsx │ │ ├── hero-section.tsx
│ │ ├── styles/ │ │ ├── styles/
│ │ ── parts/ │ │ ── parts/
│ │ └── top-banner/ │ │ └── top-banner/
│ │ ── top-banner.tsx │ │ ── top-banner.tsx
│ │ │ └── index.ts
│ │ └── index.ts
│ └── features-section/ │ └── features-section/
│ ├── features-section.tsx │ ├── features-section.tsx
── hooks/ ── hooks/
│ └── index.ts
├── home.screen.tsx ├── home.screen.tsx
└── index.ts └── index.ts
``` ```
Отличие от `ui/`: элемент `parts/` — модуль со своими сегментами. Элемент `ui/` — компонент, один `.tsx` файл. Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — компонент родительского модуля без собственной архитектурной ответственности.
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке. Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
@@ -151,4 +173,4 @@ lib/
config/ config/
├── routes.ts ├── routes.ts
└── constants.ts └── constants.ts
``` ```

View File

@@ -34,6 +34,12 @@ description: Как называть переменные, файлы и про
- `.store.ts` — стор - `.store.ts` — стор
- `.service.ts` — сервис - `.service.ts` — сервис
**Корневые компоненты слоёв**
- `.screen.tsx` — корневой компонент screen-модуля: `screens/profile/profile.screen.tsx`, компонент `ProfileScreen`
- `.layout.tsx` — корневой компонент layout-модуля: `layouts/main/main.layout.tsx`, компонент `MainLayout`
Обычные и вложенные модули не получают суффикс слоя: `ui/button/button.tsx`, `screens/profile/parts/activity-feed/activity-feed.tsx`.
**Типы и контракты** **Типы и контракты**
- `.type.ts` — типы и интерфейсы - `.type.ts` — типы и интерфейсы
- `.interface.ts` — интерфейсы - `.interface.ts` — интерфейсы

View File

@@ -9,11 +9,16 @@ description: Как типизируется код в проекте.
## Общие правила ## Общие правила
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций. - Указывать типы для параметров компонентов и параметров функций.
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов. - Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
- Избегать `any` и `unknown` без необходимости. - Избегать `any` и `unknown` без необходимости.
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины. - Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
## React-компоненты
- Пропсы компонента типизировать через отдельный `Props`.
- Возвращаемый тип компонента не указывать: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
## Функции ## Функции
- Для публичных функций указывать возвращаемый тип. - Для публичных функций указывать возвращаемый тип.

View File

@@ -13,19 +13,19 @@ keywords: [создать проект, новый проект, с нуля, in
| Компонент | Роль | Раздел | | Компонент | Роль | Раздел |
|-----------|------|--------| |-----------|------|--------|
| Next.js | Фреймворк (роутинг, сборка, SSR) | [Next.js](/docs/creating-project/nextjs) | | Next.js | Фреймворк (роутинг, сборка, SSR) | [Next.js](/docs/creating-project/nextjs) |
| Алиасы | Импорты по слоям SLM | [Алиасы](/docs/setup/aliases) | | Алиасы | Импорты по слоям SLM | [Алиасы](/docs/applied/aliases) |
| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](/docs/setup/biome) | | Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](/docs/applied/biome) |
| Стили | Глобальные токены и breakpoints | [Стили](/docs/setup/styles) | | Стили | Глобальные токены и breakpoints | [Стили](/docs/applied/styles/styles-setup) |
| PostCSS | CSS-процессор для custom-media и вложенности | [PostCSS](/docs/setup/postcss) | | PostCSS | CSS-процессор для custom-media и вложенности | [PostCSS](/docs/applied/postcss) |
| SVG-спрайты | Иконки через `<SvgSprite/>`, управление цветом | [SVG-спрайты](/docs/setup/svg-sprites) | | SVG-спрайты | Иконки через `<SvgSprite/>`, управление цветом | [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup) |
| VS Code | Настройки редактора и расширения | [VS Code](/docs/setup/vscode) | | VS Code | Настройки редактора и расширения | [VS Code](/docs/applied/vscode) |
| Шаблоны генерации | `.templates/` для `@gromlab/create` | [Шаблоны генерации](/docs/setup/templates) | | Шаблоны генерации | `.templates/` для `@gromlab/create` | [Шаблоны генерации](/docs/applied/templates/templates-setup) |
Убрать компонент из состава — значит согласованно отказаться от части стайлгайда. Частичные проекты возможны (только Next.js, Next.js + стили и т.п.), но не являются эталоном. Убрать компонент из состава — значит согласованно отказаться от части стайлгайда. Частичные проекты возможны (только Next.js, Next.js + стили и т.п.), но не являются эталоном.
## Канон раскладки ## Канон раскладки
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](/docs/usage/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)).
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`. В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
@@ -43,43 +43,43 @@ keywords: [создать проект, новый проект, с нуля, in
Заменить дефолтный `"@/*"` в `tsconfig.json` на канонический список из восьми слой-префиксов. Заменить дефолтный `"@/*"` в `tsconfig.json` на канонический список из восьми слой-префиксов.
См. [Алиасы](/docs/setup/aliases). См. [Алиасы](/docs/applied/aliases).
### 3. Biome ### 3. Biome
Линтер и форматтер. Подключается **до** написания кода, иначе в проекте копятся несогласованные правки. Линтер и форматтер. Подключается **до** написания кода, иначе в проекте копятся несогласованные правки.
См. [Biome](/docs/setup/biome). См. [Biome](/docs/applied/biome).
### 4. Стили (базовая инфраструктура) ### 4. Стили (базовая инфраструктура)
Файлы `variables.css`, `media.css`, `global.css` в `src/shared/styles/` и подключение `global.css` в `src/app/layout.tsx`. CSS-процессор на этом шаге не ставится. Файлы `variables.css`, `media.css`, `global.css` в `src/shared/styles/` и подключение `global.css` в `src/app/layout.tsx`. CSS-процессор на этом шаге не ставится.
См. [Стили](/docs/setup/styles). См. [Стили](/docs/applied/styles/styles-setup).
### 5. PostCSS ### 5. PostCSS
CSS-процессор поверх базовых стилей: `@custom-media`, вложенность, autoprefixer. Ставится **только после шага 4** — опирается на `src/shared/styles/media.css`. CSS-процессор поверх базовых стилей: `@custom-media`, вложенность, autoprefixer. Ставится **только после шага 4** — опирается на `src/shared/styles/media.css`.
См. [PostCSS](/docs/setup/postcss). См. [PostCSS](/docs/applied/postcss).
### 6. SVG-спрайты ### 6. SVG-спрайты
Пакет `@gromlab/svg-sprites`, генерация спрайт-файла и React-компонента `<SvgSprite/>`. Пакет `@gromlab/svg-sprites`, генерация спрайт-файла и React-компонента `<SvgSprite/>`.
См. [SVG-спрайты](/docs/setup/svg-sprites). См. [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup).
### 7. VS Code ### 7. VS Code
Расширения и настройки редактора. Опирается на установленный Biome (форматирование при сохранении) и PostCSS (ассоциация `*.css`). Расширения и настройки редактора. Опирается на установленный Biome (форматирование при сохранении) и PostCSS (ассоциация `*.css`).
См. [VS Code](/docs/setup/vscode). См. [VS Code](/docs/applied/vscode).
### 8. Шаблоны генерации ### 8. Шаблоны генерации
Папка `.templates/` для генератора модулей `@gromlab/create`. Папка `.templates/` для генератора модулей `@gromlab/create`.
См. [Шаблоны генерации](/docs/setup/templates). См. [Шаблоны генерации](/docs/applied/templates/templates-setup).
## Правила ## Правила

View File

@@ -35,10 +35,10 @@ npx create-next-app@latest my-app \
|------|----------|------------| |------|----------|------------|
| `--typescript` | TS включён | Стайлгайд требует TypeScript ([Типизация](/docs/basics/typing)) | | `--typescript` | TS включён | Стайлгайд требует TypeScript ([Типизация](/docs/basics/typing)) |
| `--app` | App Router | Pages Router не используется | | `--app` | App Router | Pages Router не используется |
| `--src-dir` | Код в `src/` | Архитектура SLM требует `src/` ([Структура проекта](/docs/usage/project-structure)) | | `--src-dir` | Код в `src/` | Архитектура SLM требует `src/` ([Структура проекта](/docs/applied/project-structure)) |
| `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](/docs/setup/aliases)) | | `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](/docs/applied/aliases)) |
| `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](/docs/setup/biome)) | | `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](/docs/applied/biome)) |
| `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](/docs/usage/styles)) | | `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](/docs/applied/styles/styles-usage)) |
| `--use-npm` | Пакетный менеджер — npm | Единый инструмент в проектах | | `--use-npm` | Пакетный менеджер — npm | Единый инструмент в проектах |
### 2. Очистить дефолтный шаблон ### 2. Очистить дефолтный шаблон
@@ -83,7 +83,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
### 3. Создать папку `src/shared/styles/` ### 3. Создать папку `src/shared/styles/`
Глобальные стили в SLM-архитектуре живут в слое `shared`, а не в `src/app/` ([Структура проекта](/docs/usage/project-structure)). Глобальные стили в SLM-архитектуре живут в слое `shared`, а не в `src/app/` ([Структура проекта](/docs/applied/project-structure)).
```bash ```bash
mkdir -p src/shared/styles mkdir -p src/shared/styles
@@ -95,7 +95,7 @@ mkdir -p src/shared/styles
- **Конфликт с непустой директорией** — не удалять файлы пользователя автоматически. Ставить в подпапку или временно перенести посторонние файлы. - **Конфликт с непустой директорией** — не удалять файлы пользователя автоматически. Ставить в подпапку или временно перенести посторонние файлы.
- **Отклонение от канонических флагов** (pnpm, Tailwind, ESLint и т.п.) — только осознанное, с пониманием, что стайлгайд этого не предусматривает. - **Отклонение от канонических флагов** (pnpm, Tailwind, ESLint и т.п.) — только осознанное, с пониманием, что стайлгайд этого не предусматривает.
- **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](/docs/setup/aliases)). - **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](/docs/applied/aliases)).
- **`AGENTS.md` от Next.js** удаляется в шаге 2. Повторно не создаётся. - **`AGENTS.md` от Next.js** удаляется в шаге 2. Повторно не создаётся.
- **Biome, стили, PostCSS, SVG-спрайты, VS Code** — отдельные шаги установки, не в этом разделе. - **Biome, стили, PostCSS, SVG-спрайты, VS Code** — отдельные шаги установки, не в этом разделе.

60
docs/docs/data/index.md Normal file
View File

@@ -0,0 +1,60 @@
---
title: Источники данных
description: Какие источники данных используются в проекте и как с ними работать.
keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела]
---
# Источники данных
Какие источники данных используются в проекте и как с ними работать.
## Принципы раздела
- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`.
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
## Карта раздела
### REST
Канал «запрос-ответ» по HTTP. Покрывает большинство API.
- [REST](/docs/data/rest/) — обзор раздела: создание клиента и использование.
- **Создание клиента** — как оформляется REST API в проекте:
- [Обзор](/docs/data/rest/clients/) — когда нужен клиент и как выбрать подход.
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto) — для API с OpenAPI-спецификацией, через `@gromlab/api-codegen`.
- [Ручное создание](/docs/data/rest/clients/manual) — для API без схемы, клиент пишется и поддерживается руками.
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks) — прозрачные SWR-обёртки над GET-методами клиента.
- **Использование** — как получать данные через готовый клиент:
- [Стратегии получения данных](/docs/data/rest/strategies/) — как выбрать способ получения данных под ситуацию.
- [Серверный await](/docs/data/rest/strategies/server-await) — прямой `await` метода клиента в Server Components.
- [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests) — запуск независимых серверных запросов без waterfall.
- [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down) — серверный стриминг через промис и `Suspense`.
- [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data) — серверный промис в `SWRConfig fallback`.
- [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook) — получение данных в Client Components через готовый GET-хук.
- [Business-композиция](/docs/data/rest/strategies/business-composition) — доменная интерпретация и композиция REST-данных.
### Realtime
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
- [Realtime](/docs/data/realtime) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки.
## Что даёт раздел
После прочтения раздела понятно:
- Где живёт код работы с API и почему именно там.
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infrastructure/{service-name}/hooks/`.
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
- Как подключать realtime-источники в общую модель работы с данными.
- Какие правила обязательны и какие отклонения допустимы.
## Что не входит в раздел
- **Глобальное состояние UI** — Stores, формы, фичефлаги. Это [Stores](/docs/applied/stores).
- **Доменная логика** — как данные превращаются в сценарии бизнеса. Это слой `business/` в [Архитектуре](/docs/basics/architecture/).
- **Хуки общего назначения** — переиспользуемые хуки UI, не привязанные к конкретному API. Отдельный прикладной раздел для них пока не ведётся.

View File

@@ -0,0 +1,193 @@
---
title: Автогенерация из OpenAPI
description: Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
keywords: [rest, openapi, api-codegen, автогенерация, generated, npx]
---
# Автогенерация из OpenAPI
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
## Пример API
В примерах используется Swagger Petstore:
```text
https://petstore3.swagger.io/api/v3/openapi.json
```
Имена модуля:
```text
src/infrastructure/pet-store-api/
petStoreApi
pet-store-api.generated.ts
```
## Скрипт генерации
`@gromlab/api-codegen` не устанавливается в `devDependencies`. Используем `npx @gromlab/api-codegen@latest`, чтобы запускать свежую версию.
```json
{
"scripts": {
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infrastructure/pet-store-api/generated -n pet-store-api.generated"
}
}
```
Параметры:
- `-i` — путь к OpenAPI-спецификации: URL или локальный файл.
- `-o` — директория для сгенерированного файла.
- `-n` — имя сгенерированного файла без `.ts`.
Ключ `--swr` не используется. GET-хуки REST-клиента пишутся вручную, чтобы сохранить проектный контракт: один GET-хук = один GET-метод, без бизнес-логики и композиции.
## Генерация
```bash
npm run codegen:pet-store-api
```
Ожидаемый результат:
```text
src/infrastructure/pet-store-api/generated/
└── pet-store-api.generated.ts
```
Сгенерированный файл не правится руками и коммитится в репозиторий.
## Проверка методов
После генерации откройте `generated/pet-store-api.generated.ts` и проверьте фактические имена методов.
Для Petstore нужны GET-операции вида:
```ts
petStoreApi.pet.findPetsByStatus(...)
petStoreApi.pet.getPetById(...)
```
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
## `client.ts`
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
```ts
// src/infrastructure/pet-store-api/client.ts
import { Api, HttpClient } from './generated/pet-store-api.generated'
const httpClient = new HttpClient({
baseUrl: 'https://petstore3.swagger.io/api/v3',
baseApiParams: {
secure: false,
headers: {
'Content-Type': 'application/json',
},
},
})
export const petStoreApi = new Api(httpClient)
```
В реальном проекте вместо строки `baseUrl` обычно берётся из env-переменной, нормализуется в `client.ts` и передаётся в `HttpClient` через конфиг.
`client.ts` не содержит расширения типов, `declare module` и `Extended`-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
## Расширение сгенерированных типов
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
```text
src/infrastructure/biocad-less-api/
├── generated/
│ └── biocad-less-api.generated.ts
├── types/
│ ├── term.ts
│ └── index.ts
├── client.ts
└── index.ts
```
Пример расширения generated-типа:
```ts
// src/infrastructure/biocad-less-api/types/term.ts
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
declare module '../generated/biocad-less-api.generated' {
interface TermRecordItem {
media?: {
file?: string
title?: string
url?: string
}
}
}
export type TermRecordItemExtended = Omit<
TermRecordItem,
'categories' | 'tags' | 'fields'
> & {
categories?: Array<{
_id?: string
id?: string
slug?: string
name?: string
}>
tags?: Array<{
_id?: string
id?: string
slug?: string
name?: string
}>
fields?: Record<string, unknown>
}
```
```ts
// src/infrastructure/biocad-less-api/types/index.ts
export type { TermRecordItemExtended } from './term'
```
`declare module` используется для добавления отсутствующих полей в generated-интерфейс. `Extended`-тип используется, когда нужно переопределить неточные поля, не трогая generated-файл.
## Публичный API
```ts
// src/infrastructure/pet-store-api/index.ts
export { petStoreApi } from './client'
export type { Pet } from './generated/pet-store-api.generated'
export * from './hooks'
```
Наружу импортируют только из `infrastructure/pet-store-api`, не из `generated/`.
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
```ts
// src/infrastructure/biocad-less-api/index.ts
export type { TermRecordItemExtended } from './types'
```
## Регенерация
При изменении OpenAPI-схемы:
```bash
npm run codegen:pet-store-api
```
Что меняется:
- `generated/pet-store-api.generated.ts` — перезаписывается генератором.
- `client.ts`, `hooks/`, `types/`, `index.ts` — не трогаются автоматически.
Если после регенерации поменялись сигнатуры методов или типы, это исправляется в ручном коде модуля.
## Следующий шаг
После генерации и настройки `client.ts` проверьте серверный вызов метода клиента или добавьте [GET-хук REST-клиента](/docs/data/rest/clients/hooks) для Client Components.

View File

@@ -0,0 +1,206 @@
---
title: GET-хуки REST-клиента
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
keywords: [rest, swr, get-хуки, client components, infrastructure]
---
# GET-хуки REST-клиента
GET-хуки REST-клиента — прозрачные SWR-обёртки над GET-методами API-клиента. Они нужны, чтобы Client Components получали данные с кешированием, дедупликацией и ревалидацией, не работая с `useSWR` напрямую.
## Где лежат
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
```text
src/infrastructure/
└── pet-store-api/
├── client.ts
├── generated/
├── hooks/
│ ├── use-get-pet-list.hook.ts
│ ├── use-get-pet-detail.hook.ts
│ └── index.ts
└── index.ts
```
## Контракт
- Один GET-хук = один GET-метод клиента.
- Имя GET-хука начинается с `useGet`: `useGetPetList`, `useGetPetDetail`.
- Имя файла начинается с `use-get`: `use-get-pet-list.hook.ts`.
- Хук принимает только параметры GET-метода и `config?: SWRConfiguration`.
- Что передали хуку, то он передаёт в GET-метод.
- Внутри только SWR-механика: key, fetcher, `useSWR`, `config`.
- Хук возвращает тип ответа API: generated-тип или DTO из `types/`.
- Хук не объединяет несколько запросов.
- Хук не маппит DTO в доменную модель.
- Хук не вычисляет бизнес-флаги: `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
- Хук не вызывает тосты, модалки, редиректы и не пишет UI-состояние.
## Пример списка
```ts
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
import useSWR from 'swr'
import type { SWRConfiguration } from 'swr'
import { petStoreApi } from '../client'
import type { Pet } from '../generated/pet-store-api.generated'
export type PetStatus = 'available' | 'pending' | 'sold'
export const getPetListKey = (status: PetStatus) =>
['pet-store-api', 'pet', 'list', status] as const
/**
* Получение списка питомцев по статусу.
*/
export const useGetPetList = (status: PetStatus | null, config?: SWRConfiguration) => {
const isReady = status !== null
const key = isReady ? getPetListKey(status) : null
const fetcher = () => petStoreApi.pet.findPetsByStatus({ status })
return useSWR<Pet[]>(key, fetcher, config)
}
```
Функция `getPetListKey` нужна, чтобы один и тот же SWR-ключ использовался внутри GET-хука и при передаче начальных данных через `SWRConfig fallback`.
Пример начальных данных для клиентского хука:
```tsx
import type { ReactNode } from 'react'
import { SWRConfig, unstable_serialize } from 'swr'
import {
getPetListKey,
petStoreApi,
} from 'infrastructure/pet-store-api'
export default function PetsLayout({ children }: { children: ReactNode }) {
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
return (
<SWRConfig
value={{
fallback: {
[unstable_serialize(getPetListKey('available'))]: petsPromise,
},
}}
>
{children}
</SWRConfig>
)
}
```
Клиентский компонент при этом ничего не знает про preload/fallback и продолжает вызывать обычный хук:
```tsx
const { data: pets } = useGetPetList('available')
```
## Пример detail-запроса
```ts
// src/infrastructure/pet-store-api/hooks/use-get-pet-detail.hook.ts
import useSWR from 'swr'
import type { SWRConfiguration } from 'swr'
import { petStoreApi } from '../client'
import type { Pet } from '../generated/pet-store-api.generated'
export const getPetDetailKey = (id: number) =>
['pet-store-api', 'pet', 'detail', id] as const
/**
* Получение питомца по идентификатору.
*/
export const useGetPetDetail = (id: number | null, config?: SWRConfiguration) => {
const isReady = id !== null
const key = isReady ? getPetDetailKey(id) : null
const fetcher = () => petStoreApi.pet.getPetById(id)
return useSWR<Pet>(key, fetcher, config)
}
```
## Отложенный запрос через `null`
GET-хук может принимать `null` для обязательного параметра. `null` означает, что параметр ещё не готов и запрос выполнять нельзя.
Внутри хука это выражается через `isReady`: если параметр не готов, ключ SWR становится `null`, и SWR не вызывает fetcher.
```ts
const isReady = id !== null
const key = isReady ? getPetDetailKey(id) : null
```
`null` не передаётся в метод клиента. Key-функция принимает только готовые параметры, поэтому её можно безопасно использовать для начальных данных через `SWRConfig fallback`.
Для числовых идентификаторов не используйте проверку `if (id)`: значение `0` тоже валидное число. Проверяйте явно: `id !== null`.
## Экспорт
```ts
// src/infrastructure/pet-store-api/hooks/index.ts
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
export type { PetStatus } from './use-get-pet-list.hook'
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
```
```ts
// src/infrastructure/pet-store-api/index.ts
export { petStoreApi } from './client'
export type { Pet } from './generated/pet-store-api.generated'
export * from './hooks'
```
## Где заканчивается infrastructure
```ts
// Хорошо: infrastructure, прозрачный GET-хук
const { data: pets } = useGetPetList('available')
```
```ts
// Хорошо: business, доменная интерпретация
export const useAvailablePets = () => {
const query = useGetPetList('available')
return {
...query,
hasPets: Boolean(query.data?.length),
}
}
```
`hasPets` — не часть GET-запроса, поэтому он не добавляется в `useGetPetList`.
## Что запрещено
```ts
// Плохо — useSWR в компоненте
const { data } = useSWR(
['pet-store-api', 'pet', 'list', status],
() => petStoreApi.pet.findPetsByStatus({ status }),
)
// Плохо — несколько GET внутри infrastructure-хука
export const usePetDashboard = () => {
const available = useGetPetList('available')
const sold = useGetPetList('sold')
return { available, sold }
}
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
export const useGetPetList = (status: PetStatus) => {
const query = useSWR(...)
return {
...query,
hasPets: Boolean(query.data?.length),
}
}
```
Подробное потребление таких хуков описано в стратегии [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook).

View File

@@ -0,0 +1,75 @@
---
title: Создание клиента
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
keywords: [rest, клиент, infrastructure, методы, openapi, get-хуки, swr]
---
# Создание клиента
REST-клиент — это infrastructure-модуль, через который проект работает с внешним REST API.
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
## Из чего состоит клиент
REST-клиент состоит из трёх основных частей:
1. **Клиент** — самописная оболочка над транспортом.
2. **Методы** — сгенерированные из OpenAPI или написанные вручную вызовы API.
3. **GET-хуки** — SWR-обёртки для GET-запросов.
Эти части живут в одном REST-модуле, потому что относятся к одному внешнему сервису.
## Клиент
Клиент — ручной слой, который настраивает работу с API: `baseUrl`, заголовки, авторизацию, обработку ошибок и создание инстанса сервиса.
Даже если методы генерируются из OpenAPI, `client.ts` остаётся ручным файлом проекта.
`client.ts` — только сборочная точка клиента. В нём не размещаются DTO, `declare module`, `Extended`-типы, GET-хуки и бизнес-логика.
## Методы
Методы описывают конкретные запросы к API.
Они появляются одним из двух способов:
- генерируются из OpenAPI в `generated/`;
- создаются вручную в `methods/`.
Подробности:
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto)
- [Ручное создание](/docs/data/rest/clients/manual)
## GET-хуки
Для GET-запросов добавляются GET-хуки REST-клиента.
Это прозрачные SWR-обёртки над GET-методами клиента. Они живут в `hooks/` этого же REST-модуля и нужны для использования данных в Client Components.
GET-хуки именуются с префиксом `useGet`: `useGetPetList`, `useGetPetDetail`, `useGetCurrentUser`.
Подробности:
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks)
## Структура модуля
```text
src/infrastructure/{service-name}/
├── client.ts # самописная оболочка и инстанс клиента
├── generated/ или methods/ # методы API
├── hooks/ # GET-хуки REST-клиента
├── types/ # DTO, типы API и расширения типов
├── errors/ # ошибки API, если нужны
└── index.ts # публичный API
```
`index.ts` — единственная точка входа в REST-модуль для внешнего кода.
## Что делаем дальше
1. Создайте методы клиента: [Автогенерация из OpenAPI](/docs/data/rest/clients/auto) или [Ручное создание](/docs/data/rest/clients/manual).
2. Добавьте GET-хуки для GET-запросов: [GET-хуки REST-клиента](/docs/data/rest/clients/hooks).
3. После создания клиента переходите к [Стратегиям получения данных](/docs/data/rest/strategies/).

View File

@@ -0,0 +1,187 @@
---
title: Ручное создание
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrastructure]
---
# Ручное создание
Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.
Задача ручного клиента — дать такую же точку входа, как у автогенерированного клиента: именованный API-объект, методы по сущностям, DTO и GET-хуки рядом с клиентом.
## Что нужно создать
```text
src/infrastructure/
└── pet-project-api/
├── methods/
│ └── posts.ts
├── hooks/
│ └── index.ts
├── types/
│ ├── client.ts
│ ├── post.ts
│ └── index.ts
├── errors/
│ └── pet-project-api.error.ts
├── client.ts
└── index.ts
```
| Файл | Роль |
|------|------|
| `client.ts` | Базовый транспорт и создание инстанса клиента |
| `methods/` | Методы API по сущностям |
| `types/` | DTO запросов, ответов и типы клиента |
| `errors/` | Ошибки конкретного API |
| `hooks/` | GET-хуки REST-клиента, если данные нужны в Client Components |
| `index.ts` | Публичный API REST-модуля |
## DTO и типы API
DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы.
```ts
// src/infrastructure/pet-project-api/types/post.ts
export type PostDto = {
id: string
slug: string
title: string
}
export type PostListQueryDto = {
limit?: number
category?: string
}
```
```ts
// src/infrastructure/pet-project-api/types/index.ts
export type { PostDto, PostListQueryDto } from './post'
```
Типы, которые нужны только базовому транспорту, можно держать отдельно:
```ts
// src/infrastructure/pet-project-api/types/client.ts
export type QueryParams = Record<string, string | number | boolean>
```
## Ошибка API
Ошибка API тоже относится к REST-модулю.
```ts
// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
export class PetProjectApiError extends Error {
constructor(
public readonly status: number,
message: string,
) {
super(message)
this.name = 'PetProjectApiError'
}
}
```
## Базовый клиент
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
```ts
// src/infrastructure/pet-project-api/client.ts
import { PetProjectApiError } from './errors/pet-project-api.error'
import type { QueryParams } from './types/client'
export class PetProjectApiClient {
constructor(
private readonly baseUrl: string,
private readonly defaultHeaders: Record<string, string> = {},
) {}
async get<T>(path: string, params: QueryParams = {}): Promise<T> {
const base = `${this.baseUrl.replace(/\/+$/, '')}/`
const url = new URL(path.replace(/^\/+/, ''), base)
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, String(value))
})
const response = await fetch(url, {
headers: {
Accept: 'application/json',
...this.defaultHeaders,
},
})
if (!response.ok) {
throw new PetProjectApiError(response.status, response.statusText)
}
return response.json() as Promise<T>
}
}
```
Это минимальный шаблон. Авторизация, дополнительные заголовки, `next.revalidate`, `post`, `formdata` и другие детали добавляются только когда они реально нужны API.
## Методы API
Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI.
```ts
// src/infrastructure/pet-project-api/methods/posts.ts
import type { PetProjectApiClient } from '../client'
import type { PostDto, PostListQueryDto } from '../types/post'
export function postsMethods(client: PetProjectApiClient) {
return {
/** GET /posts */
list: (query: PostListQueryDto = {}) =>
client.get<PostDto[]>('posts', query),
/** GET /posts/{slug} */
get: (slug: string) =>
client.get<PostDto>(`posts/${slug}`),
}
}
```
Метод возвращает DTO в форме API. Если данным нужен доменный смысл, маппинг делается выше, в `business/`.
## Публичный API
`index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля.
```ts
// src/infrastructure/pet-project-api/index.ts
import { PetProjectApiClient } from './client'
import { postsMethods } from './methods/posts'
const client = new PetProjectApiClient(
process.env.NEXT_PUBLIC_API_URL ?? '',
{ 'Content-Type': 'application/json' },
)
export const petProjectApi = {
posts: postsMethods(client),
}
export { PetProjectApiError } from './errors/pet-project-api.error'
export type { PostDto, PostListQueryDto } from './types'
export * from './hooks'
```
Внешний код импортирует только из `infrastructure/pet-project-api`, не из внутренних файлов модуля.
## Правила
- `fetch` используется только внутри базового клиента.
- DTO запросов и ответов живут в `types/`.
- `client.ts` не содержит DTO, GET-хуки и бизнес-логику.
- Методы лежат в `methods/` и возвращают DTO.
- GET-хуки добавляются отдельно в `hooks/`, если данные нужны в Client Components.
- Доменные типы и маппинг DTO живут не в REST-клиенте, а в `business/`.
Следующий шаг: [GET-хуки REST-клиента](/docs/data/rest/clients/hooks) или [Стратегии получения данных](/docs/data/rest/strategies/).

View File

@@ -0,0 +1,74 @@
---
title: REST
description: Как правильно работать с REST API в проекте.
keywords: [rest, api, данные, infrastructure, клиент, swr, стратегии]
---
# REST
Раздел описывает, как правильно работать с REST API в проекте: создать клиент сервиса и выбрать способ получения данных в приложении.
REST в проекте проходит через два главных этапа:
1. Создание клиента.
2. Использование.
## 1. Создание клиента
На этом этапе внешний API оформляется как модуль слоя `infrastructure/`.
Клиент отвечает за:
- генерацию или ручное описание методов API;
- настройку `baseUrl`;
- заголовки и авторизацию;
- обработку ошибок;
- кастомизацию и расширение типов;
- GET-хуки для клиентских компонентов;
- публичный API модуля.
Если у API есть OpenAPI-спецификация — клиент генерируется автоматически. Если OpenAPI нет или он неполный — клиент создаётся вручную.
GET-хуки относятся к клиенту, потому что это прозрачные SWR-обёртки над GET-методами этого клиента.
Подробнее:
- [Создание клиента](/docs/data/rest/clients/)
- [Автогенерация из OpenAPI](/docs/data/rest/clients/auto)
- [Ручное создание](/docs/data/rest/clients/manual)
- [GET-хуки REST-клиента](/docs/data/rest/clients/hooks)
## 2. Использование
После создания клиента нужно определить рендер страницы и выбрать, как получать данные в конкретном месте приложения.
Раздел использования отвечает на вопросы:
- как понять, можно ли сохранить static/ISR;
- когда страница становится dynamic/SSR;
- когда получать данные через серверный `await`;
- когда запускать несколько серверных запросов параллельно;
- когда передавать промис ниже по дереву;
- когда передавать начальные данные клиентским GET-хукам;
- когда использовать GET-хук в клиентском компоненте;
- когда выносить композицию и бизнес-смысл в `business/`.
Подробнее:
- [Стратегии получения данных](/docs/data/rest/strategies/)
- [Серверный await](/docs/data/rest/strategies/server-await)
- [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests)
- [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down)
- [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data)
- [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook)
- [Business-композиция](/docs/data/rest/strategies/business-composition)
## Как читать раздел
Если API ещё не подключён — начните с [Создания клиента](/docs/data/rest/clients/).
Если клиент уже есть, но непонятно как получить данные — начните со [Стратегий получения данных](/docs/data/rest/strategies/).
Если данные нужны в Client Component — сначала проверьте, есть ли [GET-хук REST-клиента](/docs/data/rest/clients/hooks).
Если в коде появляется бизнес-смысл вроде `isAuth`, `canEdit`, `hasAccess` — это уже не REST-клиент, а `business/`.

View File

@@ -0,0 +1,121 @@
---
title: Business-композиция
description: Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
keywords: [rest, business, композиция, hooks, domain, isAuth]
---
# Business-композиция
Business-композиция используется, когда простого GET-метода или прозрачного GET-хука недостаточно: нужно объединить несколько источников, преобразовать DTO или вычислить доменное состояние.
## Когда использовать
- Нужно объединить несколько GET-запросов.
- Нужно вычислить `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
- Нужно преобразовать DTO в доменную модель.
- Нужно спрятать бизнес-сценарий за доменным API.
Такая логика не пишется в `infrastructure/`. REST-клиент остаётся прозрачным адаптером к API.
## Пример поверх одного GET-хука
```ts
// src/business/pets/hooks/use-available-pets.hook.ts
import { useGetPetList } from 'infrastructure/pet-store-api'
/**
* Доменный список доступных питомцев.
*/
export const useAvailablePets = () => {
const query = useGetPetList('available')
return {
...query,
hasPets: Boolean(query.data?.length),
}
}
```
`useGetPetList` — infrastructure-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`.
## Пример композиции нескольких GET-хуков
```ts
// src/business/pets/hooks/use-pets-dashboard.hook.ts
import { useGetPetList } from 'infrastructure/pet-store-api'
/**
* Данные dashboard по питомцам.
*/
export const usePetsDashboard = () => {
const availablePets = useGetPetList('available')
const pendingPets = useGetPetList('pending')
const soldPets = useGetPetList('sold')
return {
availablePets,
pendingPets,
soldPets,
total:
(availablePets.data?.length ?? 0) +
(pendingPets.data?.length ?? 0) +
(soldPets.data?.length ?? 0),
}
}
```
Композиция нескольких запросов не добавляется в `infrastructure/pet-store-api/hooks/`, потому что это уже сценарий потребления данных.
## Пример auth-состояния
```ts
// src/business/auth/hooks/use-auth-state.hook.ts
import { useGetCurrentUser } from 'infrastructure/backend-api'
/**
* Состояние авторизации текущего пользователя.
*/
export const useAuthState = () => {
const currentUser = useGetCurrentUser()
const user = currentUser.data
return {
...currentUser,
user,
isAuth: Boolean(user),
}
}
```
`isAuth` не является частью REST-клиента. Это доменный смысл результата запроса.
## Где размещать
```text
src/business/
└── pets/
├── hooks/
│ └── use-available-pets.hook.ts
├── mappers/
│ └── map-pet-dto-to-pet.ts
├── types/
└── index.ts
```
Модуль `business/` экспортирует наружу готовый доменный API через `index.ts`.
## Что запрещено
```ts
// Плохо — business-смысл внутри infrastructure-хука
export const useGetPetList = (status: PetStatus) => {
const query = useSWR(...)
return {
...query,
hasPets: Boolean(query.data?.length),
}
}
```
REST-модуль отвечает за доступ к API. Business-модуль отвечает за смысл этих данных в продукте.

View File

@@ -0,0 +1,89 @@
---
title: Клиентский GET-хук
description: Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
keywords: [rest, client components, swr, get-хук, client state]
---
# Клиентский GET-хук
Клиентский GET-хук используется, когда данные зависят от состояния браузера: вкладки, фильтра, поиска, пагинации, модалки или действия пользователя.
## Когда использовать
- Запрос зависит от client state.
- Данные не обязательны для первого HTML.
- Пользователь меняет параметры запроса на клиенте.
- Нужны SWR-кеширование, дедупликация и ревалидация.
## Пример с вкладками
```tsx
'use client'
import { useState } from 'react'
import { useGetPetList } from 'infrastructure/pet-store-api'
import type { PetStatus } from 'infrastructure/pet-store-api'
const statuses: PetStatus[] = ['available', 'pending', 'sold']
export function PetTabs() {
const [status, setStatus] = useState<PetStatus>('available')
const { data: pets, isLoading, error } = useGetPetList(status)
return (
<section>
<div>
{statuses.map((item) => (
<button key={item} type="button" onClick={() => setStatus(item)}>
{item}
</button>
))}
</div>
{isLoading && <div>Загрузка...</div>}
{error && <div>Ошибка загрузки</div>}
<ul>
{pets?.map((pet) => (
<li key={pet.id}>{pet.name}</li>
))}
</ul>
</section>
)
}
```
Компонент выбирает параметр `status`, но не знает про SWR-ключ и fetcher. Запрос выполняет готовый GET-хук REST-клиента.
## Если хука нет
Хук добавляется в REST-модуль сервиса:
```text
src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
```
Не создавайте локальный `useSWR` в компоненте.
## Плохо
```tsx
// Плохо — прямой вызов клиента в useEffect
useEffect(() => {
petStoreApi.pet.findPetsByStatus({ status }).then(setPets)
}, [status])
// Плохо — useSWR в компоненте
const { data } = useSWR(
['pet-store-api', 'pet', 'list', status],
() => petStoreApi.pet.findPetsByStatus({ status }),
)
```
Такой код теряет единое место для ключей, дублирует fetcher и разносит инфраструктурные детали по UI.
## Когда выбрать другую стратегию
- Данные нужны до первого HTML — [Серверный await](/docs/data/rest/strategies/server-await).
- Клиентский хук должен получить начальные данные сразу — [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
- Нужно вычислить бизнес-состояние — [Business-композиция](/docs/data/rest/strategies/business-composition).

View File

@@ -0,0 +1,109 @@
---
title: Начальные данные для клиентских хуков
description: Как дать клиентским GET-хукам начальные REST-данные.
keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize, isr, ssr]
---
# Начальные данные для клиентских хуков
Как дать клиентским GET-хукам начальные REST-данные.
Эта стратегия используется, когда данные должны быть запущены на сервере, но потребляться на клиенте через GET-хуки REST-клиента.
Технически это делается через `SWRConfig fallback`: сервер передаёт промис в fallback, а клиентский хук использует тот же SWR-ключ.
## Когда использовать
- Внутри страницы есть Client Components с GET-хуками.
- Нужно начать загрузку данных на сервере раньше.
- Клиентский компонент должен остаться обычным потребителем `useGetPetList(...)`.
- Не нужно писать отдельный prop-drilling для начальных данных.
## Рендер страницы
Перед этой стратегией сначала определите рендер маршрута. Серверный preload для `fallback` подчиняется тем же правилам, что и любой серверный запрос в `page.tsx` или `layout.tsx`.
Если данные общие и могут обновляться по интервалу, сохраняйте static/ISR. Если preload зависит от cookie, headers, `searchParams`, `no-store` или персональных данных пользователя, маршрут становится dynamic/SSR.
`SWRConfig fallback` не должен быть причиной отключать ISR на всякий случай. Он только передаёт клиентскому GET-хуку данные, которые уже были запущены на сервере.
## Ключ хука
```ts
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
export const getPetListKey = (status: PetStatus) =>
['pet-store-api', 'pet', 'list', status] as const
```
Ключ экспортируется из REST-модуля, потому что он нужен и GET-хуку, и серверному `SWRConfig fallback`.
## Пример layout
```tsx
// src/app/(routes)/pets/layout.tsx
import type { ReactNode } from 'react'
import { SWRConfig, unstable_serialize } from 'swr'
import {
getPetListKey,
petStoreApi,
} from 'infrastructure/pet-store-api'
type PetsLayoutProps = {
children: ReactNode
}
export default async function PetsLayout({ children }: PetsLayoutProps) {
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({
status: 'available',
})
return (
<SWRConfig
value={{
fallback: {
[unstable_serialize(getPetListKey('available'))]: availablePetsPromise,
},
}}
>
{children}
</SWRConfig>
)
}
```
Если GET-хук использует array-key, ключ для `fallback` сериализуется через `unstable_serialize`.
## Клиентский компонент
```tsx
'use client'
import { useGetPetList } from 'infrastructure/pet-store-api'
export function PetList() {
const { data: pets, isLoading } = useGetPetList('available')
if (isLoading) return <div>Загрузка...</div>
return (
<ul>
{pets?.map((pet) => (
<li key={pet.id}>{pet.name}</li>
))}
</ul>
)
}
```
Компонент не знает, что данные были запущены на сервере. Он использует обычный GET-хук REST-клиента.
## Что важно
- Ключ `fallback` должен совпадать с ключом GET-хука.
- Серверный код вызывает метод клиента, а не GET-хук.
- Клиентский компонент вызывает GET-хук, а не `useSWR` напрямую.
- Эта стратегия не означает ручную работу с кешем в компонентах.
## Когда не использовать
Если данные нужны только серверному компоненту, используйте [Серверный await](/docs/data/rest/strategies/server-await). Если данные зависят от состояния браузера, используйте [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook).

View File

@@ -0,0 +1,100 @@
---
title: Стратегии получения данных
description: Как выбрать получение REST-данных с учётом рендера страницы.
keywords: [rest, стратегии, render, isr, ssr, server components, swr, promise, business]
---
# Стратегии получения данных
Как выбрать получение REST-данных с учётом рендера страницы.
Перед выбором стратегии должен быть создан REST-клиент сервиса. Если клиента ещё нет, начните с раздела [Создание клиента](/docs/data/rest/clients/).
## Сначала определите рендер страницы
В Next.js выбор начинается не с `await`, `Suspense` или SWR. Сначала нужно понять, какой рендер получится у маршрута: static/ISR или dynamic/SSR.
Next.js может перевести страницу в dynamic rendering автоматически, если в маршруте используются API текущего запроса. Поэтому первый вопрос такой:
```text
Можно ли сохранить ISR, или странице нужны данные на каждый request?
```
ISR — приоритет. Если данные общие для пользователей и их можно обновлять с интервалом, не переводите страницу в SSR без необходимости.
SSR/dynamic rendering выбирается только когда данные действительно зависят от текущего request или должны пересчитываться на каждый запрос.
## Что переводит страницу в dynamic rendering
Проверьте, нужны ли странице API и настройки, которые делают маршрут динамическим:
- `cookies()` — данные зависят от cookie текущего пользователя.
- `headers()` — данные зависят от request headers.
- `draftMode()` — нужен preview/draft-режим.
- `searchParams` в `page.tsx` — данные зависят от query string.
- `cache: 'no-store'` или `revalidate: 0` в методе клиента — запрос нельзя кешировать.
- `connection()` — рендер явно ждёт request.
- `export const dynamic = 'force-dynamic'` — SSR включён вручную.
Если ничего из этого не нужно, сначала проектируйте страницу как static/ISR. Серверный `await` сам по себе не означает SSR: режим зависит от кеширования запроса и dynamic API маршрута.
## Рендер перед стратегией
| Рендер | Когда подходит | Что выбирать дальше |
|--------|----------------|---------------------|
| Static/ISR | Данные общие и могут обновляться по интервалу | Серверные стратегии: `await`, `Promise.all`, передача промиса ниже, SWR `fallback` |
| SSR/dynamic | Данные зависят от request, пользователя или должны быть свежими на каждый запрос | Серверные стратегии с учётом блокировки первого HTML |
| После гидрации | Данные зависят от вкладки, фильтра, поиска, пагинации или действия пользователя | Клиентский GET-хук |
## Как выбрать стратегию
Когда режим рендера понятен, выбирайте конкретный способ получения данных:
| Ситуация после выбора рендера | Стратегия | Где читать |
|-------------------------------|-----------|------------|
| Данные обязательны для первого HTML, SEO, `notFound()` или `redirect()` | Серверный `await` | [Серверный await](/docs/data/rest/strategies/server-await) |
| Несколько независимых данных нужны до рендера | Запуск промисов + `Promise.all` | [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests) |
| Часть UI можно загрузить отдельно | Передача промиса ниже + `Suspense` | [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down) |
| Client Component должен получить данные сразу из SWR | Начальные данные для клиентских хуков | [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data) |
| Данные зависят от client state | Клиентский GET-хук | [Клиентский GET-хук](/docs/data/rest/strategies/client-get-hook) |
| Нужно объединить несколько запросов или вычислить `isAuth`, `canEdit`, `hasPets` | Business-композиция | [Business-композиция](/docs/data/rest/strategies/business-composition) |
## Правило выбора
Не выбирайте стратегию по любимому инструменту. Выбирайте её по двум вопросам:
```text
Можно ли сохранить ISR?
Где нужны данные и что должно произойти до первого HTML?
```
Если данные можно кешировать между пользователями — сохраняйте static/ISR. Если данные request-specific — используйте SSR/dynamic rendering. Если данные зависят от состояния браузера — используйте GET-хук REST-клиента. Если простой GET превращается в доменный сценарий — переходите в `business/`.
## Общие запреты
```tsx
// Плохо — SSR включён на всякий случай
export const dynamic = 'force-dynamic'
// Плохо — ISR отключён без требования к свежести на каждый request
export const revalidate = 0
// Плохо — прямой fetch в компоненте
useEffect(() => {
fetch('/api/pets').then(...)
}, [])
// Плохо — useSWR в компоненте
const { data } = useSWR(
['pet-store-api', 'pet', 'list', status],
() => petStoreApi.pet.findPetsByStatus({ status }),
)
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
return {
...query,
hasPets: Boolean(query.data?.length),
}
```
Не отключайте ISR без причины. В компонентах используются готовые методы клиента или готовые хуки. SWR-ключи, fetcher и транспорт остаются внутри REST-модуля.

View File

@@ -0,0 +1,82 @@
---
title: Параллельные серверные запросы
description: Как запускать независимые REST-запросы на сервере без waterfall.
keywords: [rest, promise.all, параллельные запросы, server components]
---
# Параллельные серверные запросы
Если серверному компоненту нужно несколько независимых данных, запускайте запросы до ожидания результата. Последовательный `await` создаёт waterfall и замедляет рендер.
## Когда использовать
- Запросы независимы друг от друга.
- Все данные нужны текущему серверному компоненту перед возвратом UI.
- Нельзя или не нужно стримить часть UI отдельно.
## Хорошо
```tsx
import { petStoreApi } from 'infrastructure/pet-store-api'
import { PetsDashboardScreen } from 'screens/pets-dashboard'
export default async function PetsDashboardPage() {
const availablePetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
const pendingPetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'pending' })
const soldPetsPromise = petStoreApi.pet.findPetsByStatus({ status: 'sold' })
const [availablePets, pendingPets, soldPets] = await Promise.all([
availablePetsPromise,
pendingPetsPromise,
soldPetsPromise,
])
return (
<PetsDashboardScreen
availablePets={availablePets}
pendingPets={pendingPets}
soldPets={soldPets}
/>
)
}
```
## Плохо
```tsx
export default async function PetsDashboardPage() {
const availablePets = await petStoreApi.pet.findPetsByStatus({ status: 'available' })
const pendingPets = await petStoreApi.pet.findPetsByStatus({ status: 'pending' })
const soldPets = await petStoreApi.pet.findPetsByStatus({ status: 'sold' })
return (
<PetsDashboardScreen
availablePets={availablePets}
pendingPets={pendingPets}
soldPets={soldPets}
/>
)
}
```
Во втором примере каждый следующий запрос ждёт предыдущий, хотя они независимы.
## Зависимые запросы
Если второй запрос зависит от результата первого, последовательный `await` допустим:
```tsx
export default async function OrderPage({ params }: OrderPageProps) {
const { id } = await params
const order = await petStoreApi.store.getOrderById(Number(id))
const pet = await petStoreApi.pet.getPetById(order.petId)
return <OrderScreen order={order} pet={pet} />
}
```
Не превращайте зависимый сценарий в `Promise.all` искусственно.
## Когда выбрать другую стратегию
Если часть данных не обязательна для первого блока UI, можно запустить промис выше и передать его ниже: [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down).

View File

@@ -0,0 +1,62 @@
---
title: Передача промиса ниже
description: Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
keywords: [rest, promise, suspense, streaming, server components]
---
# Передача промиса ниже
Серверный компонент может запустить запрос и передать промис вложенному server-компоненту. Это полезно, когда часть UI можно загрузить отдельно через `Suspense`.
## Когда использовать
- Верхняя часть страницы может отрендериться без этих данных.
- Данные нужны только вложенному server-компоненту.
- Нужна `Suspense`-граница и серверный стриминг.
## Пример
```tsx
// src/app/(routes)/pets/page.tsx
import { Suspense } from 'react'
import { petStoreApi } from 'infrastructure/pet-store-api'
import { PetListSection } from 'widgets/pet-list-section'
import { PetListSkeleton } from 'widgets/pet-list-section'
import type { Pet } from 'infrastructure/pet-store-api'
export default function PetsPage() {
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
return (
<main>
<h1>Питомцы</h1>
<Suspense fallback={<PetListSkeleton />}>
<AvailablePets petsPromise={petsPromise} />
</Suspense>
</main>
)
}
async function AvailablePets({ petsPromise }: { petsPromise: Promise<Pet[]> }) {
const pets = await petsPromise
return <PetListSection pets={pets} />
}
```
Запрос стартует в `PetsPage`, но ожидание происходит внутри `AvailablePets`. `Suspense` управляет fallback для этой части UI.
## Граница стратегии
Эта стратегия остаётся серверной. Не используйте её как замену GET-хукам в Client Components.
Если данные должны попасть в клиентский SWR-хук, используйте [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).
## Что не делать
```tsx
// Плохо — передавать промис в произвольный клиентский компонент без ясной стратегии
return <PetListClient petsPromise={petsPromise} />
```
Для клиентского потребления есть отдельная стратегия через `SWRConfig fallback` и готовые GET-хуки REST-клиента.

View File

@@ -0,0 +1,88 @@
---
title: Серверный await
description: Получение REST-данных на сервере до первого HTML.
keywords: [rest, server components, await, nextjs, isr, ssr, notFound, redirect]
---
# Серверный await
Получение REST-данных на сервере до первого HTML.
Серверный `await` — базовая стратегия для данных, которые нужны до рендера страницы или серверного блока.
## Когда использовать
- Данные нужны для первого HTML.
- Данные влияют на `metadata`.
- По результату запроса нужно вызвать `notFound()` или `redirect()`.
- Компонент серверный и данные не зависят от состояния браузера.
## Влияние на рендер
Серверный `await` сам по себе не означает SSR. В App Router страница может остаться static/ISR, если маршрут не использует dynamic API и запросы можно кешировать.
ISR — приоритет для общих данных. Если список или детальная страница могут обновляться по интервалу, сохраняйте кеширование и не добавляйте `no-store`, `revalidate: 0` или `force-dynamic` без требования.
SSR/dynamic rendering нужен, когда данные зависят от текущего request: cookie, headers, `searchParams`, preview-режим или персональные данные пользователя.
## Пример страницы списка
```tsx
// src/app/(routes)/pets/page.tsx
import { petStoreApi } from 'infrastructure/pet-store-api'
import { PetsScreen } from 'screens/pets'
export default async function PetsPage() {
const pets = await petStoreApi.pet.findPetsByStatus({
status: 'available',
})
return <PetsScreen pets={pets} />
}
```
`page.tsx` получает данные первого рендера и передаёт их ниже. UI страницы остаётся в `screens/`, а не пишется прямо в `app/`.
## Пример детальной страницы
```tsx
// src/app/(routes)/pets/[id]/page.tsx
import { notFound } from 'next/navigation'
import { petStoreApi } from 'infrastructure/pet-store-api'
import { PetDetailScreen } from 'screens/pet-detail'
type PetPageProps = {
params: Promise<{ id: string }>
}
export default async function PetPage({ params }: PetPageProps) {
const { id } = await params
const pet = await petStoreApi.pet.getPetById(Number(id)).catch(() => null)
if (!pet) {
notFound()
}
return <PetDetailScreen pet={pet} />
}
```
Обработка 404 зависит от API-клиента и класса ошибок. В примере показана идея: решение о `notFound()` принимается на уровне маршрута, а не внутри REST-клиента.
## Что не делать
```tsx
// Плохо — хуки нельзя вызывать в Server Component
const { data } = useGetPetList('available')
// Плохо — прямой fetch в обход клиента
const response = await fetch('https://petstore3.swagger.io/api/v3/pet/findByStatus')
```
Если данные нужны на сервере, вызывайте метод REST-клиента напрямую.
## Когда выбрать другую стратегию
- Несколько независимых запросов — [Параллельные серверные запросы](/docs/data/rest/strategies/parallel-server-requests).
- Часть UI можно грузить отдельно — [Передача промиса ниже](/docs/data/rest/strategies/pass-promise-down).
- Данные нужны клиентскому хуку сразу после гидрации — [Начальные данные для клиентских хуков](/docs/data/rest/strategies/client-hooks-initial-data).

View File

@@ -1,105 +0,0 @@
---
title: Шаблоны генерации
description: Подключение шаблонов кодогенерации в новом проекте.
keywords: [шаблоны, templates, .templates, tiged, generator, генератор шаблонов, добавить шаблон, скачать шаблоны, scaffold]
---
# Шаблоны генерации
Подключение шаблонов кодогенерации в новом проекте.
## Требования
- Доступен `npx` (ставится вместе с npm).
- Доступ к `gromlab.ru` (SSH-ключ добавлен) — для скачивания эталонного набора.
## Развилка
Сценарий зависит от состояния проекта:
| Состояние | Что делать |
|-----------|------------|
| В корне проекта **нет** `.templates/` | [Скачать стандартный набор](#скачать-стандартный-набор) |
| `.templates/` **есть**, нужен новый специфический шаблон | [Создать шаблон вручную](#создать-шаблон-вручную) |
| `.templates/` **есть**, нужно обновить до эталона | Скачать заново с подтверждением перезаписи |
## Скачать стандартный набор
### Установка
1. Проверить, что `.templates/` отсутствует (или согласовать перезапись, если папка уже есть).
2. Скачать папку из эталонного репозитория:
```bash
npx tiged git@gromlab.ru:templates/nextjs-template.git/.templates .templates
```
3. Если `tiged` падает в default-режиме (HTTP-tarball у Gitea) — повторить с явным git-режимом:
```bash
npx tiged --mode=git git@gromlab.ru:templates/nextjs-template.git/.templates .templates
```
4. Проверить генерацию:
```bash
npx @gromlab/create component test-widget src/ui
```
После проверки — удалить тестовый модуль.
### Правила
- **Скачанные файлы не править.** Источник истины — репозиторий `templates/nextjs-template`. Изменения стандартных шаблонов делаются в нём, не в отдельных проектах.
- **Расположение — только `.templates/` в корне проекта.** Это требование расширения VS Code и CLI ([Шаблоны и генерация кода](/docs/usage/templates-generation)).
- **Если доступа к `gromlab.ru` нет** — не восстанавливать содержимое из памяти: разойдётся с каноном. Запросить шаблоны иным путём.
## Создать шаблон вручную
Если стандартного набора недостаточно и нужен специфический шаблон под проект.
### Установка
1. Прочитать [Шаблоны и генерация кода](/docs/usage/templates-generation) — секции «Структура шаблонов», «Синтаксис шаблонов», «Как создать новый шаблон».
2. Определить:
- имя шаблона (папка внутри `.templates/`);
- состав файлов;
- слой SLM предполагаемых потребителей ([Архитектура: слои](/docs/basics/architecture/reference/layers));
- минимальное содержимое каждого файла.
3. Проверить, что имя шаблона не конфликтует с существующей папкой в `.templates/`.
4. Создать структуру `.templates/{name}/` по канону из [Шаблоны и генерация кода](/docs/usage/templates-generation) — синтаксис переменных, правила именования файлов и содержимого.
5. Проверить генерацию:
```bash
npx @gromlab/create {name} test-entity {целевой путь}
```
После проверки — удалить тестовый артефакт.
### Правила
- **Если запрос покрыт стандартными шаблонами** (`component`, `widget`, `store`, `layout`, `screen`, `business`) — не создавать копию.
- **Стандартные шаблоны не трогать.** Правка стандарта — задача репозитория `templates/nextjs-template`, не отдельного проекта.
- **Синтаксис переменных, правила регистра, минимальный boilerplate** — в [Шаблоны и генерация кода](/docs/usage/templates-generation). Здесь не дублируется.
## Общие правила
- VS Code-расширение `gromlab.vscode-templateFileGenerator` устанавливается разово на машину разработчика, а не через этот раздел ([Шаблоны и генерация кода](/docs/usage/templates-generation)).
- CLI `@gromlab/create` вызывается через `npx`, в `package.json` отдельно не добавляется.
- Менеджер пакетов (npm/pnpm/yarn/bun) не влияет: `tiged` и `@gromlab/create` запускаются через `npx`.
## Проверка установки
- В корне проекта есть папка `.templates/`.
- Внутри `.templates/` присутствуют стандартные шаблоны (или согласованный кастомный набор).
- В корне проекта нет каталогов `.git/` и `.github/` из репозитория-шаблона.
- Пробная генерация через `npx @gromlab/create ...` отрабатывает без ошибок.
## Дальше
- [Шаблоны и генерация кода](/docs/usage/templates-generation) — синтаксис, использование, создание новых шаблонов.

View File

@@ -1,110 +0,0 @@
---
title: Компоненты
description: Как устроен и пишется React-компонент в проекте.
---
# Компоненты
Как устроен и пишется React-компонент в проекте.
## Правила организации
1. Один компонент — один файл.
2. Компонент не содержит бизнес-логики — логика и сайд-эффекты выносятся в хуки или сторы.
3. Дочерние компоненты размещаются в сегменте `ui/` и подчиняются тем же правилам структуры.
4. Публичный API модуля — только `index.ts`. Прямые импорты внутренних файлов запрещены.
## Базовая структура компонента
Минимальный набор файлов: компонент, стили, типы и публичный экспорт.
```text
container/
├── styles/
│ └── container.module.css
├── types/
│ └── container.type.ts
├── container.tsx
└── index.ts
```
## Именования
- Имя корневого css класса всегда `.root`
- Тип пропсов именуется `{ComponentName}Props`.
- Тип пользовательских параметров именуется `{ComponentName}Params`.
## Типизация
Структура типов компонента показана в [примере](#пример). Ниже — обоснования ключевых решений.
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/docs/basics/typing).
## Реализация
- Пропсы деструктурируются в теле компонента, не в параметрах.
- Порядок: пользовательские → системные (`children`, `className`) → `...htmlAttr`.
- `className` объединяется с корневым классом через `cl()`: `cl(styles.root, className)`.
- `...htmlAttr` прокидывается на корневой элемент.
## Пример
`container/types/container.type.ts`
```ts
import type { HTMLAttributes } from 'react'
/**
* Параметры компонента Container.
*/
export type ContainerParams = {}
/** HTML-атрибуты корневого элемента. */
type RootAttrs = HTMLAttributes<HTMLDivElement>
export type ContainerProps = RootAttrs & ContainerParams
```
`container/styles/container.module.css`
```css
.root {
max-width: var(--content-width);
margin: 0 auto;
padding: 0 var(--spacing-4);
}
```
`container/container.tsx`
```tsx
import cl from 'clsx'
import type { ContainerProps } from './types/container.type'
import styles from './styles/container.module.css'
/**
* Контейнер с адаптивной максимальной шириной.
*
* Используется для:
* - обёртки контента страниц с ограничением ширины
* - центрирования блоков в лейауте
*/
export const Container = (props: ContainerProps) => {
const { children, className, ...htmlAttr } = props
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{children}
</div>
)
}
```
`container/index.ts`
```ts
export { Container } from './container'
```

View File

@@ -1,51 +0,0 @@
---
title: Источники данных
description: Какие источники данных используются в проекте и как с ними работать.
keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела]
---
# Источники данных
Какие источники данных используются в проекте и как с ними работать.
## Принципы раздела
- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`.
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые хуки модуля API (`useUserList`, `usePostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
## Карта раздела
### REST
Канал «запрос-ответ» по HTTP. Покрывает большинство API.
- **Клиенты** — как создаётся клиент REST API:
- [Автоматическая генерация](/docs/usage/data/rest/clients/auto) — для API с OpenAPI-спецификацией, через `@gromlab/api-codegen`.
- [Ручное создание](/docs/usage/data/rest/clients/manual) — для API без схемы, клиент пишется и поддерживается руками.
- **Получение данных** — как клиент используется в приложении:
- [Серверные компоненты](/docs/usage/data/rest/fetching/server) — прямой `await` метода клиента в Server Components.
- [Клиентские компоненты](/docs/usage/data/rest/fetching/client) — через готовые хуки модуля API; SWR с кешем, дедупликацией и ревалидацией скрыт внутри хука.
### Realtime
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
- [Realtime](/docs/usage/data/realtime) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки.
## Что даёт раздел
После прочтения раздела понятно:
- Где живёт код работы с API и почему именно там.
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
- Как получать данные на сервере и на клиенте, чтобы не ломать кеш и не плодить лишние запросы.
- Как подключать realtime-источники в общую модель работы с данными.
- Какие правила обязательны и какие отклонения допустимы.
## Что не входит в раздел
- **Глобальное состояние UI** — Stores, формы, фичефлаги. Это [Stores](/docs/usage/stores).
- **Доменная логика** — как данные превращаются в сценарии бизнеса. Это слой `business/` в [Архитектуре](/docs/basics/architecture/).
- **Хуки общего назначения** — переиспользуемые хуки UI, не привязанные к конкретному API. Это [Хуки](/docs/usage/hooks).

View File

@@ -1,279 +0,0 @@
---
title: Автогенерация REST-клиента
description: Генерация REST-клиента из OpenAPI-спецификации.
keywords: [api, rest, openapi, codegen, генерация, клиент, api-codegen, gromlab, infrastructure, swagger-typescript-api]
---
# Автогенерация REST-клиента
Генерация REST-клиента из OpenAPI-спецификации.
В примерах ниже используется условный API `pet-project-api` (kebab-case в путях) / `petProjectApi` (camelCase в коде). В реальном проекте имена выбираются по конкретному API.
## Установка
```bash
npm install -D @gromlab/api-codegen
```
Скрипт генерации в `package.json` — по одному на каждый API:
```json
{
"scripts": {
"codegen:pet-project-api": "api-codegen --config src/infrastructure/pet-project-api/config/pet-project-api.config.ts"
}
}
```
Конфиг и опции — в репозитории [@gromlab/api-codegen](https://gromlab.ru/gromov/api-codegen).
## Структура модуля
Клиент кладётся в слой `infrastructure/` отдельным модулем по имени API (kebab-case):
```text
src/infrastructure/
└── pet-project-api/
├── generated/ # сегмент сгенерированного кода
│ └── pet-project-api.generated.ts # сгенерировано — не править
├── types/ # расширения сгенерированных типов
│ ├── user.ts # declare module + Extended-тип
│ └── index.ts # реэкспорт расширений
├── hooks/ # SWR-хуки для клиентских компонентов
│ ├── use-user-list.hook.ts
│ ├── use-user-detail.hook.ts
│ └── index.ts # реэкспорт хуков
├── config/ # конфиги модуля
│ └── pet-project-api.config.ts # конфиг генерации клиента
├── client.ts # настройка HttpClient, инстанс Api
└── index.ts # публичный API модуля
```
| Файл | Роль | Кто правит |
|------|------|-----------|
| `generated/{service-name}.generated.ts` | Сгенерированный код: типы, `class Api`, `class HttpClient` | codegen, не править |
| `types/{сущность}.ts` | `declare module` + `Extended`-типы по сущности | разработчик |
| `types/index.ts` | Реэкспорт публичных расширений | разработчик |
| `hooks/use-{action}.hook.ts` | SWR-хук поверх метода клиента | разработчик |
| `hooks/index.ts` | Реэкспорт хуков | разработчик |
| `config/{service-name}.config.ts` | Параметры генерации для конкретного API | разработчик |
| `client.ts` | `baseUrl` из env, конфиг `HttpClient`, инстанс `new Api(...)` | разработчик |
| `index.ts` | Публичный API: инстанс сервиса, расширенные типы, хуки | разработчик |
`client.ts` и `index.ts` — единственные корневые файлы модуля. Все остальные файлы живут в сегментах (`generated/`, `types/`, `hooks/`, `config/`).
Имя сгенерированного файла — `{service-name}.generated.ts` (имя сервиса в kebab-case + суффикс `.generated.ts`). Суффикс сигнализирует «не править руками».
## `client.ts`
Тонкий ручной слой поверх сгенерированного кода. Делает три вещи: читает и нормализует `baseUrl`, конфигурирует `HttpClient`, создаёт **именованный инстанс** сервиса.
```ts
// src/infrastructure/pet-project-api/client.ts
import { Api, HttpClient } from './generated/pet-project-api.generated'
const resolvedBaseUrl = process.env.NEXT_PUBLIC_API_URL
.replace(/\/+$/, '') // убираем хвостовой слэш
.replace(/\/v1$/, '') // версия уже в путях методов — режем дубль
const httpClient = new HttpClient({
baseApiParams: {
secure: false,
headers: {
'Content-Type': 'application/json',
// кастомные заголовки API — если требуются
// 'X-App-Key': '...',
},
},
})
httpClient.baseUrl = resolvedBaseUrl
export const petProjectApi = new Api(httpClient)
```
### Имя инстанса = имя сервиса
Инстанс называется по имени API в camelCase, не унифицированно `api`/`client`. Это даёт **процедурное обращение** и однозначность при работе с несколькими сервисами:
```ts
import { petProjectApi } from 'infrastructure/pet-project-api'
const user = await petProjectApi.user.getUser(id)
```
При нескольких API — каждый со своим именем:
```ts
import { petProjectApi } from 'infrastructure/pet-project-api'
import { paymentsApi } from 'infrastructure/payments-api'
const user = await petProjectApi.user.list()
const invoice = await paymentsApi.invoices.list()
```
### Нормализация `baseUrl`
`@gromlab/api-codegen` может включать версию (`/v1`) в `baseUrl` сгенерированного кода, а пути методов уже содержат её — отсюда дубль. Стандартный приём:
```ts
.replace(/\/+$/, '') // хвостовой слэш
.replace(/\/v1$/, '') // версия (если фигурирует в путях)
```
Подгоняется под конкретный API: если версия в путях не повторяется — второй `replace` не нужен.
## Расширения типов
Автогенерация не покрывает все реальные поля API: иногда тип `object`, иногда поле просто отсутствует. Расширения живут в `types/`, по файлу на сущность.
Две техники:
### `declare module` — добавление полей
Дополняет существующий интерфейс из `generated.ts`. Сама сгенерированная декларация не трогается.
```ts
// src/infrastructure/pet-project-api/types/user.ts
import type { User } from '../generated/pet-project-api.generated'
declare module '../generated/pet-project-api.generated' {
interface User {
avatar?: {
file?: string
title?: string
url?: string
}
}
}
```
### `Extended` через `Omit & {...}` — переопределение полей
Когда автогенерация даёт `object` или общий тип, а реально структура известна — создаётся отдельный тип `UserExtended` (по имени сущности + суффикс `Extended`).
```ts
// src/infrastructure/pet-project-api/types/user.ts
export type UserExtended = Omit<User, 'roles' | 'tags' | 'fields'> & {
roles?: Array<{ _id?: string; id?: string; slug?: string; name?: string }>
tags?: Array<{ _id?: string; id?: string; slug?: string; name?: string }>
fields?: Record<string, unknown>
}
```
### Реэкспорт
```ts
// src/infrastructure/pet-project-api/types/index.ts
export type { UserExtended } from './user'
```
### Правила
- Расширения — **только в `types/`**, не в `client.ts` и не в сгенерированном файле.
- Один файл на сущность (имя файла — kebab-case по сущности: `user.ts`, `order.ts`, `invoice.ts`).
- При регенерации `generated/{service-name}.generated.ts` файлы в `types/` не затрагиваются.
- Если сломался `Extended`-тип после regen — синхронизировать руками.
## Хуки для клиентских компонентов
В клиентских компонентах вызовы клиента не делаются напрямую — компонент получает готовый хук, который инкапсулирует SWR + метод клиента. Хуки живут в сегменте `hooks/`, по файлу на операцию.
```ts
// src/infrastructure/pet-project-api/hooks/use-user-list.hook.ts
import useSWR from 'swr'
import type { SWRConfiguration } from 'swr'
import { petProjectApi } from '../client'
import type { User } from '../generated/pet-project-api.generated'
/**
* Получение списка пользователей.
*/
export const useUserList = (
query?: { limit?: number; offset?: number },
config?: SWRConfiguration,
) => {
return useSWR<User[]>(
['pet-project-api', 'user', 'list', query],
() => petProjectApi.user.list(query ?? {}),
config,
)
}
```
```ts
// src/infrastructure/pet-project-api/hooks/use-user-detail.hook.ts
import useSWR from 'swr'
import type { SWRConfiguration } from 'swr'
import { petProjectApi } from '../client'
import type { UserExtended } from '../types'
/**
* Получение пользователя по идентификатору.
*/
export const useUserDetail = (
id: string | null,
config?: SWRConfiguration,
) => {
const key = id ? ['pet-project-api', 'user', 'detail', id] : null
const fetcher = () => petProjectApi.user.getUser(id!) as Promise<UserExtended>
return useSWR<UserExtended>(key, fetcher, config)
}
```
```ts
// src/infrastructure/pet-project-api/hooks/index.ts
export { useUserList } from './use-user-list.hook'
export { useUserDetail } from './use-user-detail.hook'
```
### Правила хуков
- **Один файл — один хук**, имя файла `use-{action}.hook.ts` ([Именование](/docs/basics/naming)).
- **Тонкая обёртка над SWR.** Внутри — построение ключа, fetcher через метод клиента, возврат `useSWR(...)`. Никакой бизнес-логики.
- **Ключ начинается с имени сервиса** (`['pet-project-api', ...]`) — изолирует кеш между разными API.
- **Условный ключ для опциональных параметров:** `id ? [...key, id] : null`. `null` приостанавливает запрос, пока параметры не готовы.
- **Параметр `config?: SWRConfiguration`** — даёт потребителю переопределить ревалидацию, `fallbackData`, `suspense` и т.п. без обёрток.
## Публичный API модуля
Из `index.ts` экспортируются инстанс, расширенные типы и хуки. Сырые типы из `generated/` экспортируются по необходимости — точечно.
```ts
// src/infrastructure/pet-project-api/index.ts
export { petProjectApi } from './client'
export type { UserExtended } from './types'
export * from './hooks'
```
## Регенерация
При изменении OpenAPI-схемы:
```bash
npm run codegen:pet-project-api
```
Что меняется:
- `generated/{service-name}.generated.ts` — перезаписывается полностью, изменения коммитятся.
- `client.ts`, `types/`, `config/`, `index.ts`**не трогаются** автоматически.
Поломка контракта (изменение типов в схеме) ловится TypeScript при сборке проекта. Если ломаются `Extended`-типы — синхронизировать вручную в соответствующих файлах `types/`.
## Сгенерированный файл коммитится
Файл `generated/{service-name}.generated.ts` **не добавляется в `.gitignore`** — попадает в репозиторий вместе с остальным кодом.
Причины:
- **Детерминированная сборка.** `npm run build` не зависит от доступности OpenAPI-схемы (обычно она на удалённом сервере). Сервис лёг — прод собирается.
- **Видимость изменений в PR.** Diff показывает, что именно поменялось в контракте API между версиями.
- **Простой онбординг.** После `git clone` IDE сразу видит типы, без предварительной генерации.
- **Фиксация версии контракта.** Пересборка старого коммита даёт ровно тот клиент, что был тогда.
Регенерация — **ручная команда** при обновлении схемы, не хук `predev`/`prebuild`. Запускается осознанно.
Исключение возможно, только если OpenAPI-схема лежит **в этом же репозитории** и генерация быстрая, без сети — тогда допустимо добавить сегмент `generated/` в `.gitignore` и хук `prebuild`, по аналогии со спрайтами. На практике встречается редко.

View File

@@ -1,365 +0,0 @@
---
title: Ручное создание REST-клиента
description: "Создание REST-клиента вручную, когда нет OpenAPI-спецификации."
keywords: [api, rest, клиент, ручной, fetch, infrastructure, api-клиент]
---
# Ручное создание REST-клиента
Создание REST-клиента вручную, когда нет OpenAPI-спецификации.
В примерах ниже используется условный API `pet-project-api` / `petProjectApi`. В реальном проекте имена выбираются по конкретному API.
## Структура модуля
Клиент живёт в слое `infrastructure/` отдельным модулем по имени API (kebab-case):
```text
src/infrastructure/
└── pet-project-api/
├── methods/ # методы по сущностям API
│ ├── pages.ts
│ ├── posts.ts
│ └── forms.ts
├── hooks/ # SWR-хуки для клиентских компонентов
│ ├── use-post-detail.hook.ts
│ ├── use-post-filter.hook.ts
│ └── index.ts
├── types/ # типы клиента и доменные типы
│ ├── client.ts # типы клиента: RequestOptions, ParamValue
│ ├── post.ts # доменные типы сущности post
│ ├── form.ts # доменные типы сущности form
│ └── index.ts # реэкспорт публичных типов
├── errors/ # доменные ошибки API
│ └── pet-project-api.error.ts
├── client.ts # класс клиента: baseUrl, headers, get/post
└── index.ts # публичный API модуля
```
| Файл | Роль |
|------|------|
| `client.ts` | Класс `PetProjectApiClient`: `baseUrl`, общие заголовки, `buildUrl`, базовые `get`/`post` |
| `methods/{entity}.ts` | Методы по сущности, экспортируются фабрикой `{entity}Methods(client)` |
| `hooks/use-{action}.hook.ts` | SWR-хук поверх метода клиента |
| `hooks/index.ts` | Реэкспорт хуков |
| `types/client.ts` | Типы инфраструктуры клиента: `RequestOptions`, `PostOptions`, `ParamValue` |
| `types/{entity}.ts` | Доменные типы: запросы, ответы, фильтры по сущности |
| `types/index.ts` | Реэкспорт публичных типов |
| `errors/{service-name}.error.ts` | Доменный класс ошибок API |
| `index.ts` | Публичный API: инстанс клиента, хуки, доменные ошибки, типы |
`methods/`, `hooks/`, `types/`, `errors/` — сегменты модуля по канону SLM. `client.ts` и `index.ts` — единственные корневые файлы.
## Типы клиента
Типы, описывающие саму инфраструктуру запросов (опции, параметры) — выносятся в `types/client.ts`. Это держит `client.ts` коротким и не смешивает декларации типов с реализацией класса.
```ts
// src/infrastructure/pet-project-api/types/client.ts
export type ParamValue = string | number | (string | number)[]
export type RequestOptions = {
params?: Record<string, ParamValue>
headers?: Record<string, string>
revalidate?: number | false
}
export type PostOptions = RequestOptions & {
type?: 'json' | 'formdata'
}
```
## Базовый клиент
Класс с конфигурацией (`baseUrl`, общие заголовки) и базовыми методами `get` / `post`. Конкретные методы API размещаются в сегменте `methods/`, а не на самом классе — это держит `client.ts` коротким и не плодит «бога-класс».
```ts
// src/infrastructure/pet-project-api/client.ts
import { PetProjectApiError } from './errors/pet-project-api.error'
import type { ParamValue, RequestOptions, PostOptions } from './types/client'
export class PetProjectApiClient {
constructor(
private readonly baseUrl: string,
private readonly defaultHeaders: Record<string, string> = {},
) {
this.defaultHeaders = {
Accept: 'application/json',
...defaultHeaders,
}
}
buildUrl(path: string, params?: Record<string, ParamValue>): string {
const base = this.baseUrl.replace(/\/+$/, '')
const tail = path.replace(/^\/+/, '')
const url = `${base}/${tail}`
if (!params) {
return url
}
const search = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
value.forEach((v) => search.append(key, String(v)))
} else {
search.set(key, String(value))
}
}
return `${url}?${search}`
}
async get<T>(path: string, options: RequestOptions = {}): Promise<T> {
const { params, headers, revalidate } = options
const response = await fetch(this.buildUrl(path, params), {
headers: { ...this.defaultHeaders, ...headers },
...(revalidate !== undefined && { next: { revalidate } }),
})
if (!response.ok) {
throw await PetProjectApiError.fromResponse(response)
}
return response.json() as Promise<T>
}
async post<T>(path: string, body: unknown, options: PostOptions = {}): Promise<T> {
const { params, headers, revalidate, type = 'json' } = options
const isJson = type === 'json'
const response = await fetch(this.buildUrl(path, params), {
method: 'POST',
headers: {
...this.defaultHeaders,
...(isJson && { 'Content-Type': 'application/json' }),
...headers,
},
body: isJson ? JSON.stringify(body) : (body as BodyInit),
...(revalidate !== undefined && { next: { revalidate } }),
})
if (!response.ok) {
throw await PetProjectApiError.fromResponse(response)
}
return response.json() as Promise<T>
}
}
```
### Ключевые требования к клиенту
- **Класс с приватным состоянием** (`baseUrl`, `defaultHeaders`) — конфигурация инкапсулирована.
- **Типы клиента — в `types/client.ts`**, не в `client.ts`. Реализация и контракты разделены.
- **Базовые методы дженерик `<T>` без дефолта.** Вызов без типа невозможен — потребитель обязан указать форму ответа.
- **Доменная ошибка вместо `null`.** При не-`ok` бросается `PetProjectApiError`. Возврат `null` глотает причины (404 vs 500 vs 401) — не использовать.
- **Дефолт POST — `json`.** `formdata` указывается явно, на конкретных методах (загрузка файлов, отправка форм).
- **Нормализация слэшей** в `buildUrl``baseUrl` без хвостового `/`, `path` без ведущего `/`.
- **`async/await`**, не `.then()` — линейное чтение, простая обработка ошибок.
- **Поддержка `next.revalidate`** — клиент знает о Next.js App Router и пробрасывает кеш-флаги.
## Доменная ошибка
Сетевая ошибка превращается в класс ошибки модуля. Наружу не выходит сырой `Response`.
```ts
// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
export class PetProjectApiError extends Error {
constructor(
public readonly status: number,
public readonly body: string,
) {
super(`PetProjectApi ${status}: ${body.slice(0, 200)}`)
this.name = 'PetProjectApiError'
}
static async fromResponse(response: Response): Promise<PetProjectApiError> {
const body = await response.text().catch(() => '')
return new PetProjectApiError(response.status, body)
}
}
```
Дополнительные подклассы по необходимости: `PetProjectApiValidationError` (400), `PetProjectApiAuthError` (401/403), `PetProjectApiNotFoundError` (404). Вводятся когда у потребителя есть **разная реакция** на разные коды; иначе хватает базового класса.
## Доменные типы
Типы запросов, ответов и фильтров — по файлу на сущность. Типы должны лежать рядом по смыслу: всё, что относится к `posts`, — в `types/post.ts`.
```ts
// src/infrastructure/pet-project-api/types/post.ts
export type Post = {
id: string
slug: string
title: string
content: string
publishedAt: string
}
export type PostFilter = {
limit?: number
categories?: number[]
}
```
```ts
// src/infrastructure/pet-project-api/types/index.ts
export type * from './post'
export type * from './form'
// типы клиента — внутренние, наружу не реэкспортируются
```
Типы клиента (`RequestOptions`, `PostOptions`, `ParamValue`) **не реэкспортируются** через `types/index.ts` — они нужны только внутри модуля.
## Методы
Методы группируются по сущностям в сегменте `methods/`, экспортируются фабрикой, принимающей клиент. Это даёт **процедурное обращение** в стиле автогенерированного клиента (`petProjectApi.posts.get(slug)`), а не плоский список (`petProjectApi.getPost(slug)`).
```ts
// src/infrastructure/pet-project-api/methods/posts.ts
import type { PetProjectApiClient } from '../client'
import type { Post, PostFilter } from '../types/post'
export function postsMethods(client: PetProjectApiClient) {
return {
/** GET /posts/{slug} */
get: (slug: string, options?: { revalidate?: number | false }) =>
client.get<Post>(`posts/${slug}`, options),
/** POST /posts/filter */
filter: (body: PostFilter) =>
client.post<Post[]>('posts/filter', body),
}
}
```
```ts
// src/infrastructure/pet-project-api/methods/forms.ts
import type { PetProjectApiClient } from '../client'
import type { Form, FormSubmissionResult } from '../types/form'
export function formsMethods(client: PetProjectApiClient) {
return {
/** GET /forms/{id} */
get: (id: string) => client.get<Form>(`forms/${id}`),
/** POST /forms/{id} — multipart/form-data */
submit: (id: string, data: FormData) =>
client.post<FormSubmissionResult>(`forms/${id}`, data, { type: 'formdata' }),
}
}
```
### Правила методов
- **Группировка по сущности** (`pages`, `posts`, `forms`), не плоский список.
- **Имя метода — глагол действия**: `get`, `list`, `filter`, `create`, `update`, `delete`, `submit`. Не `getPost`/`getPosts` — сущность уже в имени группы.
- **Типы запросов и ответов — в `types/{entity}.ts`**, импортируются в файл методов. В `methods/` лежит только композиция вызовов клиента, без объявлений типов.
- **Фабрика принимает клиент** — это даёт тестируемость (моковый клиент в юнит-тестах) и единый источник конфигурации.
- **Никаких знаний об UI.** Клиент не знает про React, SWR, тосты — только данные и ошибки.
## Сборка инстанса
Группы методов соединяются в один объект на уровне `index.ts`. Это даёт процедурный доступ `petProjectApi.posts.get(...)`.
```ts
// src/infrastructure/pet-project-api/index.ts
import { PetProjectApiClient } from './client'
import { pagesMethods } from './methods/pages'
import { postsMethods } from './methods/posts'
import { formsMethods } from './methods/forms'
const client = new PetProjectApiClient(process.env.NEXT_PUBLIC_API_URL, {
'X-App-Key': process.env.NEXT_PUBLIC_APP_KEY,
})
export const petProjectApi = {
pages: pagesMethods(client),
posts: postsMethods(client),
forms: formsMethods(client),
}
export { PetProjectApiError } from './errors/pet-project-api.error'
export type { Post, PostFilter, Page, Form } from './types'
export * from './hooks'
```
## Хуки для клиентских компонентов
В клиентских компонентах вызовы клиента не делаются напрямую — компонент получает готовый хук, который инкапсулирует SWR + метод клиента. Хуки живут в сегменте `hooks/`, по файлу на операцию.
```ts
// src/infrastructure/pet-project-api/hooks/use-post-detail.hook.ts
import useSWR from 'swr'
import type { SWRConfiguration } from 'swr'
import { petProjectApi } from '..'
import type { Post } from '../types/post'
/**
* Получение поста по slug.
*/
export const usePostDetail = (
slug: string | null,
config?: SWRConfiguration,
) => {
const key = slug ? ['pet-project-api', 'post', 'detail', slug] : null
const fetcher = () => petProjectApi.posts.get(slug!)
return useSWR<Post>(key, fetcher, config)
}
```
```ts
// src/infrastructure/pet-project-api/hooks/use-post-filter.hook.ts
import useSWR from 'swr'
import type { SWRConfiguration } from 'swr'
import { petProjectApi } from '..'
import type { Post, PostFilter } from '../types/post'
/**
* Получение списка постов по фильтру.
*/
export const usePostFilter = (
filter: PostFilter,
config?: SWRConfiguration,
) => {
return useSWR<Post[]>(
['pet-project-api', 'post', 'filter', filter],
() => petProjectApi.posts.filter(filter),
config,
)
}
```
```ts
// src/infrastructure/pet-project-api/hooks/index.ts
export { usePostDetail } from './use-post-detail.hook'
export { usePostFilter } from './use-post-filter.hook'
```
### Правила хуков
- **Один файл — один хук**, имя файла `use-{action}.hook.ts` ([Именование](/docs/basics/naming)).
- **Тонкая обёртка над SWR.** Внутри — построение ключа, fetcher через метод клиента, возврат `useSWR(...)`. Никакой бизнес-логики.
- **Ключ начинается с имени сервиса** (`['pet-project-api', ...]`) — изолирует кеш между разными API.
- **Условный ключ для опциональных параметров:** `id ? [...key, id] : null`. `null` приостанавливает запрос, пока параметры не готовы.
- **Параметр `config?: SWRConfiguration`** — даёт потребителю переопределить ревалидацию, `fallbackData`, `suspense` и т.п. без обёрток.
## Запрет прямого `fetch`
В коде приложения (слои выше `infrastructure`) прямые вызовы `fetch` к API запрещены. Все запросы идут через клиент.
Исключение допускается точечно — например, разовая отладочная проверка эндпоинта в скрипте — и требует обоснования в коде (комментарий с причиной).
## Использование
```ts
import { petProjectApi } from 'infrastructure/pet-project-api'
const post = await petProjectApi.posts.get('my-post')
const list = await petProjectApi.posts.filter({ limit: 10, categories: [1, 2] })
const form = await petProjectApi.forms.get('contact')
```
Стиль вызовов совпадает с автогенерированным клиентом — потребитель не различает, ручной API или сгенерирован.

View File

@@ -1,164 +0,0 @@
---
title: Клиентские компоненты
description: Получение REST-данных в клиентских компонентах.
keywords: [swr, клиентские компоненты, useSWR, хук, мутация, useSWRMutation, кеш, ревалидация]
---
# Клиентские компоненты
Получение REST-данных в клиентских компонентах.
## Правила
- **Только готовые хуки.** В компоненте — `usePostDetail(slug)`, не `useSWR(['post', slug], () => api.posts.get(slug))`.
- **`useSWR` пишется один раз — в `hooks/`** модуля API. В клиентских компонентах никогда напрямую.
- **Прямой вызов методов клиента в `useEffect` запрещён.** Это потеря кеша, повторные запросы и гонки.
- **Мутации — через `useSWRMutation`**, тоже инкапсулированный в хуке. В компоненте вызывается готовый `trigger`.
## Чтение
```tsx
'use client'
import { usePostDetail } from 'infrastructure/pet-project-api'
export function PostView({ slug }: { slug: string }) {
const { data: post, error, isLoading } = usePostDetail(slug)
if (isLoading) return <Spinner />
if (error) return <ErrorView error={error} />
return <article>{post?.title}</article>
}
```
В компоненте нет `useSWR`, нет ключей, нет fetcher — только готовый хук.
## Параметризованный запрос
Хук сам обрабатывает «нет параметра — нет запроса». В компоненте можно безопасно передавать `null`:
```tsx
'use client'
import { useUserDetail } from 'infrastructure/pet-project-api'
export function UserProfile({ userId }: { userId: string | null }) {
const { data: user } = useUserDetail(userId)
if (!userId) return <EmptyState />
return <UserCard user={user} />
}
```
Внутри `useUserDetail` ключ становится `null`, когда `userId` не задан, и SWR не делает запрос — это поведение зашито в хук, потребитель об этом не думает.
## Мутации
Мутации тоже оборачиваются в хук модуля API:
```ts
// src/infrastructure/pet-project-api/hooks/use-create-user.hook.ts
import useSWRMutation from 'swr/mutation'
import { mutate } from 'swr'
import { petProjectApi } from '..'
import type { User, UserCreateInput } from '../types'
/**
* Создание пользователя с инвалидацией списка.
*/
export const useCreateUser = () => {
return useSWRMutation<User, Error, [string, string, string], UserCreateInput>(
['pet-project-api', 'user', 'create'],
(_key, { arg }) => petProjectApi.user.create(arg),
{
onSuccess: () => mutate(['pet-project-api', 'user', 'list']),
},
)
}
```
```tsx
'use client'
import { useCreateUser } from 'infrastructure/pet-project-api'
export function CreateUserForm() {
const { trigger, isMutating } = useCreateUser()
return (
<Form
onSubmit={(input) => trigger(input)}
disabled={isMutating}
/>
)
}
```
В компоненте — снова только хук. Логика инвалидации кеша зашита внутрь, потребитель её не дублирует.
## Передача config из компонента
Каждый хук принимает второй (или третий) параметр `config?: SWRConfiguration` — он пробрасывается в `useSWR`. Это даёт потребителю точечно настроить ревалидацию, `fallbackData`, `suspense` и т.п.:
```tsx
'use client'
import { usePostDetail } from 'infrastructure/pet-project-api'
export function PostView({ slug, initialPost }: Props) {
const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
// ...
}
```
## Начальное состояние с сервера
Если данные пришли из серверного компонента (см. [Серверные компоненты](/docs/usage/data/rest/fetching/server)) — передаются в `fallbackData` через `config` хука:
```tsx
// page.tsx (server)
import { petProjectApi } from 'infrastructure/pet-project-api'
export default async function Page({ params }: { params: { slug: string } }) {
const initialPost = await petProjectApi.posts.get(params.slug)
return <PostView slug={params.slug} initialPost={initialPost} />
}
```
```tsx
// post-view.tsx ('use client')
import { usePostDetail } from 'infrastructure/pet-project-api'
export function PostView({ slug, initialPost }: Props) {
const { data: post } = usePostDetail(slug, { fallbackData: initialPost })
return <article>{post?.title}</article>
}
```
Для массового заполнения кеша на странице с несколькими хуками — используется `<SWRConfig fallback>` обёртка. Серверный компонент собирает данные и передаёт сериализованную карту ключей в провайдер; все вложенные хуки сразу видят кеш.
## Запрет прямых вызовов
```tsx
// Плохо — прямой fetch в обход клиента
useEffect(() => {
fetch('/api/users').then(...)
}, [])
// Плохо — клиент без SWR: нет кеша, нет дедупликации
useEffect(() => {
petProjectApi.user.list().then(setUsers)
}, [])
// Плохо — useSWR в компоненте: SWR должен быть в хуке модуля
const { data } = useSWR(
['pet-project-api', 'user', 'list'],
() => petProjectApi.user.list(),
)
// Хорошо — готовый хук модуля
const { data } = useUserList()
```
Если для нужной операции хука ещё нет — он добавляется в `hooks/` модуля API, не в компонент.

View File

@@ -1,66 +0,0 @@
---
title: Серверные компоненты
description: Получение REST-данных в серверных компонентах.
keywords: [server components, rsc, серверные компоненты, fetch, api, app router, прямой вызов]
---
# Серверные компоненты
Получение REST-данных в серверных компонентах.
## Правила
- **Прямой `await` метода клиента.** Никаких хуков, обёрток состояний, `useEffect` — серверный компонент не имеет жизненного цикла React-клиента.
- **Ошибки бросаются.** Не оборачивать `try/catch` без необходимости — Next.js поднимет ближайший `error.tsx`.
- **Параллельные запросы — через `Promise.all`.** Последовательный `await` за `await` блокирует рендер.
## Шаблон
```tsx
// src/app/(routes)/users/page.tsx
import { petProjectApi } from 'infrastructure/pet-project-api'
export default async function UsersPage() {
const users = await petProjectApi.user.list()
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
```
## Параллельные запросы
```tsx
export default async function DashboardPage() {
const [users, orders] = await Promise.all([
petProjectApi.user.list(),
petProjectApi.order.list(),
])
return <Dashboard users={users} orders={orders} />
}
```
## Передача данных в клиентский компонент
Серверный компонент получает данные и передаёт их пропсами в клиентский. На клиенте данные становятся начальным состоянием — при необходимости перезапрашиваются через SWR (см. [Клиентские компоненты](/docs/usage/data/rest/fetching/client)).
```tsx
// page.tsx (server)
import { petProjectApi } from 'infrastructure/pet-project-api'
import { UsersList } from 'widgets/users-list'
export default async function UsersPage() {
const initialUsers = await petProjectApi.user.list()
return <UsersList initialUsers={initialUsers} />
}
```
## Запрет прямого `fetch`
Серверный компонент тоже использует только клиент из `infrastructure/`. Прямой `fetch` в `page.tsx` или в server-action запрещён теми же правилами, что и на клиенте.

View File

@@ -1,63 +0,0 @@
---
title: Файлы роутинга
description: "Что должно лежать в файлах роутинга, а что — в экранах."
---
# Файлы роутинга
Что должно лежать в файлах роутинга, а что — в экранах.
## Организация
- `page.tsx` — тонкий файл: только `metadata` и рендер экрана. Логика, стили и зависимости живут в экране, не в `page.tsx`.
- `error.tsx` и `not-found.tsx` делегируют разметку экранам по тому же принципу.
- `layout.tsx` — точка подключения провайдеров и глобальных стилей. Вёрстка layout-обёрток выносится в слой `layouts/`.
- Стили в файлах роутинга не используются — стилизация только внутри вызываемых компонентов.
## Реализация
- Каждый `page.tsx` экспортирует `metadata` с `title` — он подставляется в шаблон корневого layout (`%s | App`).
- Корневой `layout.tsx` задаёт `metadata` с `title.template`, `description`, `metadataBase` и OpenGraph-настройками.
## Примеры
`src/app/profile/[id]/page.tsx`
```tsx
import type { Metadata } from 'next'
import { ProfileScreen } from '@/screens/profile'
export const metadata: Metadata = {
title: 'Профиль',
description: 'Страница профиля пользователя',
}
type ProfilePageProps = {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return <ProfileScreen id={id} />
}
```
`src/app/error.tsx`
```tsx
'use client'
import { ErrorScreen } from '@/screens/error'
type ErrorPageProps = {
error: Error & { digest?: string }
reset: () => void
}
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
return <ErrorScreen error={error} reset={reset} />
}
export default ErrorPage
```

View File

@@ -1,156 +0,0 @@
---
title: Шаблоны и генерация кода
description: Как устроены шаблоны кодогенерации и как ими пользоваться.
---
<!-- @formatter:off -->
::: v-pre
# Шаблоны и генерация кода
Как устроены шаблоны кодогенерации и как ими пользоваться.
## Структура шаблонов
Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон.
```text
.templates/
├── component/ # шаблон компонента
│ └── {{name.kebabCase}}/
│ ├── styles/
│ │ └── {{name.kebabCase}}.module.css
│ ├── types/
│ │ └── {{name.kebabCase}}.type.ts
│ ├── {{name.kebabCase}}.tsx
│ └── index.ts
└── store/ # шаблон Zustand стора
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.store.ts
├── {{name.kebabCase}}.type.ts
└── index.ts
```
## Синтаксис шаблонов
Переменные работают в именах файлов/папок и внутри файлов. Базовая переменная — `name`.
```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}}.type.ts
import type { HTMLAttributes } from 'react'
/**
* Параметры {{name.pascalCase}}.
*/
export type {{name.pascalCase}}Params = {}
/** HTML-атрибуты корневого элемента. */
type RootAttrs = HTMLAttributes<HTMLDivElement>
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
```
```tsx
// .templates/component/{{name.kebabCase}}.tsx
import cl from 'clsx'
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}.type'
import styles from './styles/{{name.kebabCase}}.module.css'
/**
* {{name.pascalCase}}.
*/
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
const { children, className, ...htmlAttr } = props
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
{children}
</div>
)
}
```
```css
/* .templates/component/styles/{{name.kebabCase}}.module.css */
.root {
}
```
## Генерация через VS Code
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
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 module auth src/business` | Бизнес-модуль |
| `npx @gromlab/create widget header src/widgets` | Виджет |
| `npx @gromlab/create layout admin src/layouts` | Layout |
| `npx @gromlab/create store auth src/business/auth/stores` | Стор |
:::

View File

@@ -227,7 +227,6 @@ const copyDirSync = (
const srcPath = path.join(src, entry.name); const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name); const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) { if (entry.isDirectory()) {
fs.mkdirSync(destPath, { recursive: true });
count += copyDirSync(srcPath, destPath, filter); count += copyDirSync(srcPath, destPath, filter);
} else if (entry.isFile() && filter(entry.name)) { } else if (entry.isFile() && filter(entry.name)) {
fs.mkdirSync(dest, { recursive: true }); fs.mkdirSync(dest, { recursive: true });
@@ -250,6 +249,8 @@ const copyMdFiles = (): void => {
const destDir = path.join(PUBLIC_DIR, 'docs'); const destDir = path.join(PUBLIC_DIR, 'docs');
if (!fs.existsSync(srcDir)) return; if (!fs.existsSync(srcDir)) return;
fs.rmSync(destDir, { recursive: true, force: true });
const copied = copyDirSync( const copied = copyDirSync(
srcDir, srcDir,
destDir, destDir,
@@ -321,6 +322,8 @@ const transformLinksInDir = (rootDir: string): void => {
* в архив как есть. * в архив как есть.
*/ */
const buildZip = (): void => { const buildZip = (): void => {
fs.rmSync(path.resolve(PUBLIC_DIR, 'nextjs-style-guide'), { recursive: true, force: true });
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-')); const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
const stage = path.join(tmpRoot, 'nextjs-style-guide'); const stage = path.join(tmpRoot, 'nextjs-style-guide');
fs.mkdirSync(stage, { recursive: true }); fs.mkdirSync(stage, { recursive: true });