2 Commits
main ... v2

Author SHA1 Message Date
47658cdbb9 sync 2026-04-20 09:43:43 +03:00
4aeb1dd6b2 feat: Progressive Disclosure документация 2026-04-20 06:40:34 +03:00
103 changed files with 2408 additions and 6684 deletions

View File

@@ -20,9 +20,6 @@ jobs:
echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV
REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')" REGISTRY_IMAGE="$DOCKER_REGISTRY/$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')"
echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV echo "REGISTRY_IMAGE=$REGISTRY_IMAGE" >> $GITHUB_ENV
# Версия сборки: тег если есть, иначе короткий SHA
BUILD_VERSION=$(git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD)
echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
- name: Login to Container Registry - name: Login to Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@@ -50,8 +47,6 @@ jobs:
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: |
BUILD_VERSION=${{ env.BUILD_VERSION }}
provenance: false provenance: false
sbom: false sbom: false
@@ -78,7 +73,7 @@ jobs:
ssh -i ~/.ssh/deploy_key root@188.225.47.78 bash -s <<'SCRIPT' ssh -i ~/.ssh/deploy_key root@188.225.47.78 bash -s <<'SCRIPT'
set -e set -e
IMAGE="${{ env.REGISTRY_IMAGE }}:latest" IMAGE="${{ env.REGISTRY_IMAGE }}:latest"
CONTAINER="nextjs-style-guide" CONTAINER="frontend-style-guide"
# Логин в реестр # Логин в реестр
echo '${{ secrets.CR_TOKEN }}' | docker login ${{ env.DOCKER_REGISTRY }} -u '${{ secrets.CR_USER }}' --password-stdin echo '${{ secrets.CR_TOKEN }}' | docker login ${{ env.DOCKER_REGISTRY }} -u '${{ secrets.CR_USER }}' --password-stdin

6
.gitignore vendored
View File

@@ -134,8 +134,4 @@ dist
# VitePress # VitePress
.vitepress/cache .vitepress/cache
.vitepress/dist .vitepress/dist
docs/.vitepress docs/.vitepress
# Генерируется через `npm run llms`
docs/public/
generated/

View File

@@ -1,189 +1,112 @@
import { defineConfig } from 'vitepress'; import { defineConfig } from 'vitepress';
const sidebar = [ const ruSidebar = [
{ {
text: 'Главная', text: 'Workflow',
link: '/docs/', link: '/workflow',
},
{
text: 'Подсказки',
link: '/docs/workflow',
}, },
{ {
text: 'Базовые правила', text: 'Базовые правила',
items: [ items: [
{ text: 'Технологии и библиотеки', link: '/docs/basics/tech-stack' }, { text: 'Технологии и библиотеки', link: '/basics/tech-stack' },
{ text: 'Именование', link: '/docs/basics/naming' }, { text: 'Именование', link: '/basics/naming' },
{ { text: 'Архитектура', link: '/basics/architecture' },
text: 'Архитектура', { text: 'Стиль кода', link: '/basics/code-style' },
collapsed: true, { text: 'Документирование', link: '/basics/documentation' },
items: [ { text: 'Типизация', link: '/basics/typing' },
{ text: 'Обзор', link: '/docs/basics/architecture/' },
{ text: 'Слои', link: '/docs/basics/architecture/layers' },
{ text: 'Модули', link: '/docs/basics/architecture/modules' },
{ text: 'Сегменты', link: '/docs/basics/architecture/segments' },
],
},
{ text: 'Стиль кода', link: '/docs/basics/code-style' },
{ text: 'Документирование', link: '/docs/basics/documentation' },
{ text: 'Типизация', link: '/docs/basics/typing' },
],
},
{
text: 'Создание проекта',
items: [
{ text: 'Из шаблона', link: '/docs/creating-project/from-template' },
{ text: 'По гайду вручную', link: '/docs/creating-project/manual' },
{ text: 'Чистый Next.js', link: '/docs/creating-project/nextjs' },
],
},
{
text: 'Работа с данными',
// collapsed: true,
items: [
{ text: 'Введение', link: '/docs/data/' },
{
text: 'REST',
collapsed: true,
items: [
{ text: 'Обзор', link: '/docs/data/rest/' },
{
text: 'Создание клиента',
collapsed: true,
items: [
{ text: 'Обзор', link: '/docs/data/rest/clients/' },
{ 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: 'Использование',
collapsed: true,
items: [
{ text: 'Стратегии получения данных', link: '/docs/data/rest/strategies/' },
{ 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/data/realtime' },
], ],
}, },
{ {
text: 'Прикладные разделы', text: 'Прикладные разделы',
items: [ items: [
{ text: 'Структура проекта', link: '/docs/applied/project-structure' }, { text: 'Структура проекта', link: '/applied/project-structure' },
{ text: 'Страницы', link: '/docs/applied/page-level' }, { text: 'Компоненты', link: '/applied/components' },
{ text: 'Компонент', link: '/docs/applied/component' }, { text: 'Страницы (App Router)', link: '/applied/page-level' },
{ text: 'Модуль', link: '/docs/applied/module' }, { text: 'Шаблоны и генерация кода', link: '/applied/templates-generation' },
{ { text: 'Стили', link: '/applied/styles' },
text: 'Стили', { text: 'Изображения', link: '/applied/images-sprites' },
collapsed: true, { text: 'SVG-спрайты', link: '/applied/svg-sprites' },
items: [ { text: 'Видео', link: '/applied/video' },
{ text: 'Настройка', link: '/docs/applied/styles/styles-setup' }, { text: 'API', link: '/applied/api' },
{ text: 'Использование', link: '/docs/applied/styles/styles-usage' }, { text: 'Stores', link: '/applied/stores' },
], { text: 'Хуки', link: '/applied/hooks' },
}, { text: 'Шрифты', link: '/applied/fonts' },
{ { text: 'Локализация', link: '/applied/localization' },
text: 'SVG-спрайты', { text: 'Настройка VS Code', link: '/applied/vscode' },
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 · в разработке' }
], ],
}, },
]; ];
/** const enSidebar = [
* Vite-плагин: отдаёт `.txt` и `.md` с явной кодировкой UTF-8. {
* Без этого браузер декодирует как ISO-8859-1 и кириллица ломается. text: 'Processes',
*/ items: [
const utf8TextPlugin = { { text: 'Getting Started', link: '/en/workflow/getting-started' },
name: 'utf8-text-files', { text: 'Creating an App', link: '/en/workflow/creating-app' },
configureServer(server: any) { { text: 'Creating Pages', link: '/en/workflow/creating-pages' },
server.middlewares.use((req: any, res: any, next: any) => { { text: 'Creating Components', link: '/en/workflow/creating-components' },
const url: string = req.url || ''; { text: 'Styling', link: '/en/workflow/styling' },
if (url.endsWith('.txt') || url.endsWith('.md')) { { text: 'Data Fetching', link: '/en/workflow/data-fetching' },
res.setHeader('Content-Type', 'text/plain; charset=utf-8'); { text: 'State Management', link: '/en/workflow/state-management' },
} { text: 'Localization', link: '/en/workflow/localization' },
next(); ],
});
}, },
}; {
text: 'Basic Rules',
items: [
{ text: 'Tech Stack', link: '/en/basics/tech-stack' },
{ text: 'Architecture', link: '/en/basics/architecture' },
{ text: 'Code Style', link: '/en/basics/code-style' },
{ text: 'Naming', link: '/en/basics/naming' },
{ text: 'Documentation', link: '/en/basics/documentation' },
{ text: 'Typing', link: '/en/basics/typing' },
],
},
{
text: 'Applied Sections',
items: [
{ text: 'VS Code Setup', link: '/en/applied/vscode' },
{ text: 'Project Structure', link: '/en/applied/project-structure' },
{ text: 'Components', link: '/en/applied/components' },
{ text: 'Page-level Components', link: '/en/applied/page-level' },
{ text: 'Templates & Code Generation', link: '/en/applied/templates-generation' },
{ text: 'Styles', link: '/en/applied/styles' },
{ text: 'Images', link: '/en/applied/images-sprites' },
{ text: 'SVG Sprites', link: '/en/applied/svg-sprites' },
{ text: 'Video', link: '/en/applied/video' },
{ text: 'API', link: '/en/applied/api' },
{ text: 'Stores', link: '/en/applied/stores' },
{ text: 'Hooks', link: '/en/applied/hooks' },
{ text: 'Fonts', link: '/en/applied/fonts' },
{ text: 'Localization', link: '/en/applied/localization' },
],
},
];
export default defineConfig({ export default defineConfig({
srcDir: 'docs', srcDir: 'docs',
// `docs/public/` содержит сгенерированные `.md`-копии и `llms.txt` для LLM
// (попадают в корень `dist/` как статика). Исключаем из сканирования
// страниц, иначе VitePress рендерит их как HTML-страницы.
//
// `DEVELOP.md` и `MAP.md` — файлы архива (точка входа и карта).
// Содержат относительные ссылки, на сайте им делать нечего —
// эту роль выполняют сайдбар и `llms.txt`.
srcExclude: ['public/**', '**/DEVELOP.md', '**/MAP.md'],
lang: 'ru-RU',
title: 'NextJS Style Guide', title: 'NextJS Style Guide',
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM', description: 'Правила и стандарты разработки на NextJS и TypeScript',
// Чистые URL без `.html` — канон для индексации. rewrites: {
// Серверная поддержка реализована в Caddyfile (try_files + редирект). 'ru/:rest*': ':rest*',
cleanUrls: true, },
// Дублируем указатель на llms.txt в <head> — для агентов, locales: {
// которые читают HTML, но не парсят полный DOM/href. root: {
head: [ label: 'Русский',
['link', { rel: 'alternate', type: 'text/plain', href: '/llms.txt', title: 'llms.txt' }], lang: 'ru-RU',
['link', { rel: 'alternate', type: 'text/plain', href: '/llms-full.txt', title: 'llms-full.txt' }], themeConfig: {
], sidebar: ruSidebar,
},
vite: { },
plugins: [utf8TextPlugin], en: {
define: { label: 'English',
__BUILD_VERSION__: JSON.stringify(process.env.BUILD_VERSION || 'dev'), lang: 'en-US',
link: '/en/',
themeConfig: {
sidebar: enSidebar,
},
}, },
}, },
});
themeConfig: {
sidebar,
socialLinks: [
{ icon: 'github', link: 'https://gromlab.ru/docs/nextjs-style-guide' },
],
},
// Расширенный блок описания для llms.txt — даёт LLM полный
// технический контекст: стек, методология, охват тем.
// Используется в generate-llms.ts.
llmsBlockquote:
'Стандарты разработки frontend-приложений на Next.js (App Router) + TypeScript + React с архитектурой SLM (Scoped Layered Module Design — модульная архитектура со слоями ответственности, где каждый модуль содержит всё необходимое: компоненты, хуки, сторы, типы, стили).',
llmsContext:
'Стек: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nДокументация покрывает архитектуру SLM (слои, модули, сегменты, направление зависимостей, публичный API), правила оформления кода (именование, форматирование, импорты, типизация, JSDoc), реализацию компонентов и хуков, работу с App Router, кодогенерацию из шаблонов, стилизацию (Mobile First, токены), работу с API и сокетами, управление состоянием через Zustand, локализацию, ассеты (шрифты, изображения, SVG-спрайты) и настройку VS Code.',
} as any);

View File

@@ -15,5 +15,3 @@
max-width: 100%; max-width: 100%;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }

View File

@@ -3,6 +3,11 @@
При работе с документацией следовать правилам из CONTRIBUTING.md. При работе с документацией следовать правилам из CONTRIBUTING.md.
- Язык документации и коммитов — русский. - Язык документации и коммитов — русский.
- После изменений в `.md`-файлах — запустить `npm run llms` для обновления `llms.txt` и README. - Исходники документации — в `src/`, только `.md` файлы.
- При добавлении нового раздела — обновить сайдбар (`.vitepress/config.ts`): - Скрипты и манифесты сборки — в `scripts/`.
он же является источником порядка и группировки для `llms.txt`. - Общие правила — в `src/base/`.
- Фреймворк-специфичные — в `src/{framework}/`.
- Точки входа (`DEVELOP.md`, `REVIEW.md`) — в `src/{framework}/`.
- После изменений в `.md`-файлах — запустить `npm run build:ai` для пересборки `dist/ai/`.
- При добавлении нового раздела — добавить файл в `src/` и путь в манифест `scripts/{fw}.build.js`.
- Frontmatter каждого `.md`-файла содержит поля `title`, `scope`, `keywords`, `when`.

View File

@@ -1,390 +1,129 @@
# Правила работы над документацией # Правила написания документации
Мета-документ: как устроен проект, как писать и редактировать разделы. Как писать и редактировать разделы стайлгайда.
## О проекте
Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript.
- Движок: VitePress
- Язык: русский
- Контент: `docs/docs/`
## Команды
| Команда | Что делает |
|---------|-----------|
| `npm run dev` | Локальный сервер разработки |
| `npm run build` | Сборка статического сайта |
| `npm run llms` | Генерация `llms.txt` (карта документации для LLM) и README |
## Структура файлов
```
docs/
├── index.md # Лендинг (URL `/`)
└── docs/ # Контент документации (URL `/docs/...`)
├── index.md # Главная страница
├── workflow.md # Подсказки
├── basics/ # Базовые правила: каким должен быть код
│ ├── tech-stack.md
│ ├── architecture/
│ ├── code-style.md
│ ├── naming.md
│ ├── documentation.md
│ └── typing.md
├── creating-project/ # Создание проекта: как поднять новый проект
│ ├── from-template.md
│ ├── manual.md
│ └── nextjs.md
├── data/ # Работа с данными
│ ├── index.md
│ ├── realtime.md
│ └── rest/
└── applied/ # Прикладные разделы: настройка и использование
├── project-structure.md
├── page-level.md
├── component.md
├── module.md
├── styles/ # Стили: настройка + использование
│ ├── styles-setup.md
│ └── styles-usage.md
├── svg-sprites/ # SVG-спрайты: введение + настройка + использование
│ ├── svg-sprites-intro.md
│ ├── svg-sprites-setup.md
│ └── svg-sprites-usage.md
├── images.md
├── fonts.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/
└── config.ts # Конфигурация VitePress, сайдбар
generate-llms.ts # Скрипт генерации llms.txt и README
```
Сгенерированные артефакты (`docs/public/`): `llms.txt`, `llms-full.txt`,
`nextjs-style-guide.zip`, `manifest.json`, копии `.md` в `docs/public/docs/`.
### Добавление нового раздела
1. Создать `.md`-файл в нужной папке: `basics/`, `creating-project/`,
или `applied/`.
2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
Сайдбар — единственный источник порядка и группировки для `llms.txt`.
3. Запустить `npm run llms` для обновления `llms.txt` и README.
## Типы разделов ## Типы разделов
Документация разделена на четыре группы. Каждая отвечает на свой вопрос
и имеет свою природу — это влияет на содержимое и структуру страницы.
### Базовые правила (`basics/`) ### Базовые правила (`basics/`)
**Отвечает на вопрос:** «Каким должен быть любой код?» **Отвечает на вопрос:** «Каким должен быть любой код?»
Универсальные стандарты, **не привязанные к конкретной области**. Универсальные стандарты, не привязанные к конкретной области.
Правило базовое, если оно применимо ко всему коду одинаково: именование Правило базовое, если оно применимо ко всему коду одинаково.
переменных, оформление импортов, когда использовать `type` vs `interface`.
Примеры в базовых правилах допускаются, но служат иллюстрацией принципа, **Граница:** если правило касается только одной области — оно прикладное.
а не инструкцией по конкретной области.
**Граница:** если правило касается только одной области (только стили,
только компоненты, только API) — оно живёт в прикладном разделе, не в базовых.
### Создание проекта (`creating-project/`)
**Отвечает на вопрос:** «Как поднять новый проект?»
Сценарии запуска нового проекта целиком: из шаблона, вручную, чистая
установка фреймворка. Раздел описывает порядок шагов на уровне всего
проекта; детали отдельных инструментов лежат в `applied/`.
**Граница:** не дублирует разделы `applied/`. Ссылается на них как на
шаги в общем сценарии.
### Прикладные разделы (`applied/`) ### Прикладные разделы (`applied/`)
**Отвечает на вопрос:** «Как поставить инструмент и как им пользоваться **Отвечает на вопрос:** «Как работать с X
Прикладные разделы объединяют настройку и использование инструментов Полное описание конкретной области: структура файлов, правила, именование, типизация, примеры.
и подсистем. Каждый раздел — самостоятельная предметная область.
Разделы делятся на два типа: **Граница:** не дублирует базовые правила. Если правило уже описано в базовых — ссылается, но не повторяет.
1. **Только настройка** — разовая установка инструмента (линтер, ### Триггеры (`triggers/`)
CSS-процессор, алиасы). Файл без суффикса: `biome.md`, `postcss.md`.
2. **Настройка + использование** — область, требующая и установки, **Отвечает на вопрос:** «Как выполнить задачу X?»
и повседневных правил. Два файла с суффиксами: `styles-setup.md`
(настройка) и `styles-usage.md` (использование). В сайдбаре
оборачиваются в collapsed-группу.
**Граница:** прикладной раздел не дублирует базовые правила. Если правило Конкретная инструкция: какие разделы прочитать, какие шаги выполнить. Триггер ссылается на basics/ и applied/, но не дублирует их. Группируются по роли: `triggers/develop/`, `triggers/review/`, `triggers/architect/`.
уже описано в `basics/` — прикладной раздел ссылается на него, но не
повторяет.
## Структура прикладного раздела Структура триггера:
Шаблон ниже относится к usage-страницам прикладных разделов (`applied/*-usage.md`). - **Заголовок** — глагол + объект ("Создать компонент", "Добавить иконку")
Setup-страницы (`applied/*-setup.md`) и `creating-project/` имеют другую - **Описание** — одно предложение: что делает триггер
структуру — ориентированную на пошаговую установку (требования → установка → - **Прочитай перед началом** — ссылки на basics/ и applied/
проверка). - **Шаги** — нумерованный список действий со ссылками
- **Смежные триггеры** — ссылки на связанные задачи
- **Проверь себя** — чеклист из 2-5 пунктов для самопроверки
Шаблон описывает все допустимые секции. Раздел включает только те, ### Фреймворк-специфичные (`{framework}/`)
которые для него релевантны — пустые секции не создаются.
```markdown Разделы и триггеры, которые применимы только к конкретному фреймворку. Те же категории — `applied/` и `triggers/`.
# {Название}
{Одно-два предложения, по которым читатель за секунду решает, нужен ли ему раздел. **Граница:** если правило одинаково для всех фреймворков — оно в `base/`.
Правила оформления — секция «Заголовок и описание» ниже.}
## Что нужно знать ## Frontmatter
Неочевидная информация, которую читатель должен знать перед чтением раздела.
Если для раздела нет такой вводной — секция не создаётся.
## Структура
Файловая организация: какие файлы создавать и куда класть.
Обязательно — дерево файлов через code-block.
## Правила
Конкретные требования, специфичные для области. Делятся на две подсекции:
### Реализация
Как написан код внутри файла: синтаксис, паттерны, API.
Отвечает на вопрос: «Как писать код?»
Примеры: объявление через `const`, деструктуризация пропсов, формат вызова `cl()`, способ подключения стилей, структура хука.
### Организация
Как компонент/модуль встроен в проект: файловые границы, зоны ответственности, экспорт.
Отвечает на вопрос: «Где что лежит и за что отвечает?»
Примеры: один компонент — один файл, вложенные компоненты в `ui/`, логика выносится в `model/`.
Формат обеих подсекций — маркированный список.
Для неочевидных случаев — блоки «Хорошо / Плохо».
Если в области нет правил одной из категорий — подсекция не создаётся.
## Именование
Соглашения по именам, специфичные для этой области.
Только то, что НЕ покрыто в базовом разделе «Именование».
## Типизация
Правила типизации, специфичные для этой области.
Только то, что НЕ покрыто в базовом разделе «Типизация».
## Документирование
Что и как документировать в этой области.
Только то, что НЕ покрыто в базовом разделе «Документирование».
## Примеры
Полноценные примеры кода.
Каждый пример с путём к файлу и пояснениями.
```
### Порядок секций
Порядок фиксированный: контекст → структура → правила → специализации базовых правил → примеры.
Логика: читатель сначала понимает «что это», потом «где это лежит», потом «как это делать», и в конце видит полный пример.
### Секции-расширения базовых правил
«Именование», «Типизация», «Документирование» в прикладном разделе — это **точки расширения** базовых правил.
- В базовых описано общее: `camelCase` для переменных, `type` vs `interface`, формат JSDoc.
- В прикладном разделе описано специфичное: как именовать CSS-классы (стили), как типизировать пропсы компонентов (компоненты), как документировать хуки (хуки).
Если для области нет специфики по именованию, типизации или документированию — секция не создаётся.
## Конвенции оформления
### Frontmatter
Каждый `.md`-файл начинается с YAML frontmatter: Каждый `.md`-файл начинается с YAML frontmatter:
```yaml ```yaml
--- ---
title: Название раздела title: Название раздела
description: Описание раздела одним предложением. scope: basics | applied | triggers
keywords: [ключевое слово 1, ключевое слово 2]
when: "Когда агенту читать этот раздел"
--- ---
``` ```
- `title` совпадает с текстом `h1`-заголовка в файле. | Поле | Описание |
- `description` совпадает с абзацем-описанием сразу под `h1`. |------|----------|
| `title` | Название раздела. Совпадает с `h1` в файле |
| `scope` | Тип: `basics`, `applied` или `triggers` |
| `keywords` | Ключевые слова для поиска агентом |
| `when` | Описание ситуации, когда раздел релевантен |
Подробнее о требованиях к самому заголовку и описанию — секция ## Структура прикладного раздела
«Заголовок и описание» ниже.
### Заголовок и описание Раздел включает только релевантные секции — пустые не создаются.
Каждая страница начинается с `h1`-заголовка и абзаца-описания сразу под ним.
Эта пара — **навигационный маркер**: попадает в сайдбар, `llms.txt`,
`MAP.md` архива и должна за секунду давать читателю или LLM понять,
**когда сюда нужно идти**.
#### Структура заголовков
- Один `h1` на файл, совпадает с `title` во frontmatter.
- Сразу после `h1` — описание (одно-два предложения, см. ниже).
- Основные секции — `h2`.
- Подсекции внутри `h2``h3`.
- `h4` не используется.
#### Имя `h1` (заголовок страницы)
- Называет предметную область, а не реализацию.
- Без имён пакетов, опций, конфигов и путей.
- Самодостаточен — читается без контекста сайдбара.
- Исключение: имя инструмента допустимо, если оно — единственное
устойчивое имя самой области (`PostCSS`, `Biome`, `VS Code`).
- Если страница вложена в семантическую группу
(`Архитектура → Слои`, `Данные → REST → Серверные компоненты`)
и короткое имя теряет смысл при прямой ссылке — `h1` поднимает
имя родителя в заголовок: `Слои SLM`, `Сегменты SLM`. В сайдбаре
допустимо оставить короткий вариант (`Слои`, `Сегменты`) — там
путь группы виден через дерево.
- Подъём в заголовок применяется только когда читается грамматически
естественно (`Слои SLM`, `Автогенерация REST-клиента`). Если
получается натянутая конструкция (`REST в серверных компонентах`) —
заголовок остаётся коротким, а контекст полностью переносится
в описание. Заголовок и описание — пара: если один не несёт
контекст, его обязательно несёт второй.
**Хорошо:** «Алиасы импортов», «Структура проекта», «SVG-спрайты»,
«Слои SLM», «Автогенерация REST-клиента».
**Плохо:** «Установка и настройка» (что устанавливаем?),
«Использование» (что используем?), «Введение» (во что?),
«Сегменты» (чего сегменты?), «REST в серверных компонентах»
(грамматически натянуто — лучше короткий h1 + контекст в описании).
#### Описание
Описание — короткий ответ на вопрос «у меня задача X, мне сюда?».
Не аннотация. Не оглавление.
**Запреты:**
- Не упоминать конкретные пакеты, библиотеки, инструменты
(`@gromlab/svg-sprites`, `Mantine`, `Zustand`).
- Не упоминать конкретные файлы и пути
(`postcss.config.mjs`, `.templates/`, `biome.json`).
- Не упоминать конкретные опции, ключи API, имена функций
(`baseUrl`, `cl()`, `useStore`).
- Не начинать с «Раздел описывает», «Этот раздел»,
«В этом разделе», «Здесь рассмотрено».
- Не использовать дежурные префиксы как шаблон
(«Правила работы с...», «Правила написания...») — само то,
что раздел про правила, и так понятно из секции и заголовка.
- Не пересказывать содержимое перечислением подсекций
(«организация, реализация, делегирование, метаданные»).
- Не аргументировать пользу
(«обеспечит единообразие», «упростит поддержку»).
**Требования:**
- 1 предложение, обычно 512 слов.
- Звучит как ответ человека другу, а не как техспек.
- Описание читается **самостоятельно**, без контекста сайдбара.
- Если страница вложена в семантическую группу
(например, `Данные → REST → Клиенты → ...`) и её заголовок
без этой группы теряет смысл — описание явно содержит имя
родительской области, чтобы читалось без сайдбара.
**Подходящие формы:**
- «Как X.»
- «Что такое X.»
- «Из чего состоит X.»
- «Установка X.»
- «Какие X есть и как ими пользоваться.»
Перечисление аспектов через двоеточие — только если без него читатель
не сможет различить раздел от соседнего.
**Тест навигации.** Читатель видит описание — за секунду должен понять
«мне сюда» или «нет, не сюда». Если приходится перечитывать —
описание слишком длинное.
**Тест на изменение.** Если в разделе сменится пакет, переименуется
файл или добавится правило — придётся ли править описание?
Если да — оно слишком конкретное.
**Хорошо:**
```markdown ```markdown
Какие алиасы импортов есть в проекте и как ими пользоваться. # {Название}
Краткое описание: о чём раздел.
## Что нужно знать
Неочевидная вводная информация (если есть).
## Структура
Файловая организация. Обязательно — дерево файлов.
## Правила
### Реализация
Как писать код: синтаксис, паттерны, API.
### Организация
Где что лежит: файловые границы, зоны ответственности, экспорт.
## Именование
Специфичные для области соглашения (не покрытые в basics/naming).
## Типизация
Специфичные для области правила (не покрытые в basics/typing).
## Документирование
Специфичные для области правила (не покрытые в basics/documentation).
## Примеры
Полноценные примеры кода с путями к файлам.
``` ```
```markdown Порядок фиксированный: контекст -> структура -> правила -> специализации базовых -> примеры.
Установка и настройка линтера-форматтера в новом проекте.
```
```markdown ## Конвенции оформления
Из чего состоит проект и где что лежит.
```
```markdown ### Заголовки
Получение REST-данных в серверных компонентах.
```
**Плохо:** - Один `h1` на файл — совпадает с `title` из frontmatter.
- Сразу после `h1` — вводный абзац.
```markdown - Основные секции — `h2`. Подсекции — `h3`. `h4` не используется.
Раздел описывает, какие алиасы используются в проекте: их полный список,
где они объявлены и как ими пользоваться между модулями и внутри модуля.
```
_Начинается с «Раздел описывает», пересказывает содержимое._
```markdown
Установка и настройка CSS-процессора PostCSS в проекте: набор плагинов,
конфиг `postcss.config.mjs`. Выполняется один раз при заведении проекта.
```
_Упомянут конкретный файл, перечисление аспектов превратилось в оглавление._
```markdown
Правила работы с React-компонентами: файловая структура,
типизация пропсов, документирование, реализация.
```
ежурный префикс «Правила работы с...» плюс оглавление подсекций._
### Примеры кода ### Примеры кода
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `. - Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.
- Путь к файлу указывается перед блоком кода текстом или комментарием внутри блока. - Путь к файлу перед блоком кода или комментарием внутри.
- Дерево файлов — ` ```text ` с символами `├──`, `└──`, `│`. - Дерево файлов — ` ```text ` с символами `├──`, `└──`, `│`.
### Блоки «Хорошо / Плохо» ### Блоки «Хорошо / Плохо»
Используются для контрастного сравнения правильного и неправильного подхода.
Формат:
```markdown ```markdown
**Хорошо:** **Хорошо:**
@@ -401,16 +140,16 @@ _Дежурный префикс «Правила работы с...» плюс
### Таблицы ### Таблицы
Используются для структурированных перечислений: настройки, команды, соответствия.
Формат — стандартный Markdown: `| Ключ | Описание |`. Формат — стандартный Markdown: `| Ключ | Описание |`.
### Ссылки между разделами ### Ссылки между разделами
Прикладной раздел может ссылаться на другие разделы, но не дублирует их содержимое. Ссылаться можно, дублировать содержимое — нет.
## Принципы ## Принципы
1. **Не дублировать.** Одна мысль живёт в одном месте. Остальные ссылаются. 1. **Не дублировать.** Одна мысль одно место. Остальные ссылаются.
2. **Базовое vs прикладное.** Если правило применимо ко всему коду — оно базовое. Если только к одной области — прикладное. 2. **Базовое vs прикладное.** Применимо ко всему коду — базовое. К одной области — прикладное.
3. **Пустые секции не создавать.** Если для раздела нет специфики по именованию — секции «Именование» в нём нет. 3. **Общее vs специфичное.** Одинаково для всех фреймворков — в `base/`. Для одного — в `{framework}/`.
4. **Примеры обязательны.** Прикладной раздел без примеров кода — незавершён. 4. **Пустые секции не создавать.**
5. **Примеры обязательны.** Прикладной раздел без примеров — незавершён.

View File

@@ -1,44 +1,5 @@
:8080 { :8080 {
root * /srv root * /srv
# Устаревшие пути llms.txt в подпапках → корень.
# Без этого опечатка `/docs/llms.txt` уходит в SPA-фолбэк и
# отдаёт HTML под видом text/plain — агент верит, что получил llms.txt.
redir /docs/llms.txt /llms.txt 301
redir /docs/llms-full.txt /llms-full.txt 301
# Чистые URL: запросы вида `/docs/foo.html` редиректим на `/docs/foo`.
# Канон сайта — без `.html` (cleanUrls в VitePress).
# Не трогаем index.html в корне — он не имеет смысловой пары без расширения.
@legacyHtml {
path_regexp legacyHtml ^(/.+)\.html$
not path /index.html
}
redir @legacyHtml {re.legacyHtml.1} 301
# Подсказка агентам, где лежит карта документации (RFC 8288).
# Позволяет найти llms.txt без парсинга DOM — по HTTP-заголовку.
header Link "</llms.txt>; rel=\"llms\""
# Кириллица в .txt/.md ломается без явного charset.
# Применяем заголовок только к РЕАЛЬНО существующим файлам,
# иначе SPA-фолбэк (HTML) уезжает с Content-Type: text/plain.
@existingText {
path *.txt *.md
file
}
header @existingText Content-Type "text/plain; charset=utf-8"
# Несуществующие .txt/.md → 404, не HTML-фолбэк.
# Это критично для llms.txt: агент должен получить честный 404,
# а не валидный «как бы текст» с лендингом внутри.
@missingText {
path *.txt *.md
not file
}
respond @missingText 404
file_server file_server
# cleanUrls: пробуем точное совпадение → +.html → каталог → SPA-фолбэк. try_files {path} /index.html
try_files {path} {path}.html {path}/ /index.html
} }

View File

@@ -1,13 +1,9 @@
FROM node:24-alpine AS build FROM node:24-alpine AS build
WORKDIR /app WORKDIR /app
# zip нужен для упаковки nextjs-style-guide.zip
RUN apk add --no-cache zip
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm ci
COPY . . COPY . .
ARG BUILD_VERSION=dev RUN npm run build
ENV BUILD_VERSION=${BUILD_VERSION}
RUN npm run llms && npm run build
FROM caddy:2-alpine FROM caddy:2-alpine
COPY Caddyfile /etc/caddy/Caddyfile COPY Caddyfile /etc/caddy/Caddyfile

119
README.md
View File

@@ -1,94 +1,53 @@
# NextJS Style Guide # Style Guide
Стандарты разработки фронтенд-приложений на Next.js и TypeScript. Репозиторий с правилами и стандартами фронтенд-разработки. Исходники документации собираются в разные форматы под разные фреймворки.
Сайт: https://nextjs-style-guide.gromlab.ru ## Структура
## Использование ```text
src/ # Исходники — только .md файлы
├── base/ # Общие правила (не поставляется отдельно)
│ ├── basics/ # Базовые: стиль кода, именование, типизация
│ ├── applied/ # Прикладные: компоненты, стили, хуки, API
│ └── triggers/ # Триггеры: создание компонента, стилизация и т.д.
└── nextjs/ # Next.js — самостоятельная единица
├── applied/ # Next.js-специфичные: page-level, project-structure
├── triggers/ # Next.js-специфичные триггеры: create-page, create-layout
├── DEVELOP.md # Точка входа для агента-разработчика
└── REVIEW.md # Точка входа для агента-ревьювера
**Для AI-агентов:** scripts/ # Скрипты и манифесты сборки
├── build-ai.js # Скрипт сборки
└── nextjs.build.js # Манифест: какие файлы, куда, как называются
- [llms.txt](https://nextjs-style-guide.gromlab.ru/llms.txt) — Карта разделов, оглавление со ссылками на разделы. dist/ # Собранные версии (gitignore)
- [llms-full.txt](https://nextjs-style-guide.gromlab.ru/llms-full.txt) — Вся документация одним файлом. ├── ai/{framework}/ # Для AI-агентов
└── vitepress/{framework}/ # Для людей (планируется)
```
**Для проекта:** ## Сборка
- [nextjs-style-guide.zip](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта. ```bash
npm run build:ai # Собрать все фреймворки
```
## Структура документации ## Манифест
### Подсказки Каждый фреймворк имеет манифест `scripts/{framework}.build.js`. Ключ — путь в выходной папке, значение — путь исходника в `src/`.
[Подсказки](docs/docs/workflow.md) — короткие ответы на типовые вопросы и решения для спорных ситуаций. Скрипт только копирует файлы по манифесту. Никакой генерации.
### Базовые правила ## Добавление раздела
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии. 1. Создать `.md` в `src/base/` (общий) или `src/{framework}/` (специфичный).
2. Добавить frontmatter: `title`, `scope`, `keywords`, `when`.
3. Добавить путь в манифест `scripts/{framework}.build.js`.
4. Обновить точку входа (`DEVELOP.md` и/или `REVIEW.md`).
5. `npm run build:ai`.
| Раздел | Отвечает на вопрос | ## Добавление фреймворка
|--------|-------------------|
| Технологии и библиотеки | Какой стек используем? |
| Именование | Как называть файлы, переменные, компоненты, хуки? |
| SLM Design | Что такое SLM и зачем она нужна? |
| Архитектура: Слои | Какие слои есть и как между ними устроены зависимости? |
| Архитектура: Модули | Что такое модуль и как он устроен? |
| Архитектура: Сегменты | Какие сегменты есть внутри модуля? |
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
| Документирование | Как писать JSDoc: что документировать, а что нет? |
| Типизация | Как типизировать: type vs interface, any/unknown? |
### Создание проекта 1. Создать `src/{framework}/` с `.md` файлами и точками входа.
2. Создать `scripts/{framework}.build.js`.
**Как начать новый проект** — варианты установки и эталонный набор инструментов. 3. `npm run build:ai`.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
### Настройка
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Алиасы импортов | Как настроить алиасы импортов? |
| Biome | Как настроить линтер и форматтер? |
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
| Стили | Как подключить базовые стили и токены? |
| SVG-спрайты | Как подключить генерацию SVG-спрайтов? |
| Шаблоны генерации | Как подключить шаблоны для кодогенерации? |
| VS Code | Как настроить редактор для проекта? |
### Использование
**Как это устроено и как этим пользоваться** — структура, примеры и правила для конкретных областей.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Структура проекта | Как организованы папки и файлы по SLM? |
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
| Стили | Как писать CSS: вложенность, медиа, токены? |
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
| Изображения | _(не заполнен)_ |
| Видео | _(не заполнен)_ |
| Stores | _(не заполнен)_ |
| Хуки | _(не заполнен)_ |
| Шрифты | _(не заполнен)_ |
| Локализация | _(не заполнен)_ |
### Данные
**Как работать с источниками данных** — REST, realtime и потребление в компонентах.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Источники данных | Как устроена работа с данными в проекте? |
| REST: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
| REST: Ручное создание | Как написать REST-клиент вручную? |
| REST: Серверные компоненты | Как получать данные в серверных компонентах? |
| REST: Клиентские компоненты | Как получать данные в клиентских компонентах? |
| Realtime | Как работать с realtime-каналами и сокетами? |

View File

@@ -1,103 +0,0 @@
---
title: Гид для агента
description: Что AI-агент обязан прочитать перед началом работы, а что — по задаче.
---
# Обязательное чтение перед началом работы
Этот документ определяет **строгий порядок действий агента перед выполнением любых задач**.
## Общее правило
Перед началом работы над **любой задачей** агент **обязан ознакомиться с базовой документацией проекта**.
Нарушение этого порядка считается ошибкой.
---
## Порядок обязательного чтения
Агент должен читать документацию **строго в следующем порядке**:
### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО)
* [Архитектура: Обзор](./basics/architecture/index.md)
* [Архитектура: Слои](./basics/architecture/layers.md)
* [Архитектура: Модули](./basics/architecture/modules.md)
* [Архитектура: Сегменты](./basics/architecture/segments.md)
**Архитектура — это самое важное в проекте.**
Агент обязан:
* строго понимать архитектурный подход (SLM)
* соблюдать архитектуру **на 100% без отклонений**
* не предлагать решений, нарушающих архитектурные принципы
* не упрощать архитектуру даже ради скорости выполнения задачи
Любое нарушение архитектуры недопустимо.
---
### 2. Базовые правила
После архитектуры необходимо изучить:
* [Технологии и библиотеки](./basics/tech-stack.md)
* [Именование](./basics/naming.md)
* [Стиль кода](./basics/code-style.md)
* [Документирование](./basics/documentation.md)
* [Типизация](./basics/typing.md)
Агент обязан применять эти правила во всех решениях.
---
## Использование карты документации
Для поиска дополнительных сведений агент должен использовать:
* [MAP.md](./MAP.md)
MAP.md содержит ссылки на все прикладные и вспомогательные разделы.
Агент может:
* переходить к нужным разделам через MAP.md
* уточнять детали реализации
* искать примеры и частные случаи
---
## Запрещено
Агенту запрещено:
* начинать выполнение задачи без изучения архитектуры
* игнорировать базовые правила
* принимать решения, противоречащие архитектуре
* придумывать собственные подходы, если они не описаны в документации
---
## Ожидаемое поведение агента
Перед выполнением задачи агент должен:
1. Изучить архитектуру
2. Изучить базовые правила
3. При необходимости открыть MAP.md и найти релевантные разделы
4. Только после этого приступать к решению задачи
---
## Приоритеты
При принятии решений агент должен руководствоваться следующим приоритетом:
1. **Архитектура**
2. Базовые правила
3. Документация из MAP.md
4. Задача пользователя
Если задача противоречит архитектуре — задача должна быть переосмыслена, а не выполнена напрямую.

View File

@@ -1,66 +0,0 @@
# Карта документации
Список всех разделов архива с относительными ссылками. Точка входа
`DEVELOP.md` рядом с этим файлом.
## Подсказки
- [Подсказки](./workflow.md) — Короткие ответы на типовые вопросы и решения для спорных ситуаций.
## Базовые правила
- [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте.
- [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде.
- [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен.
- [Архитектура: Слои](./basics/architecture/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны.
- [Архитектура: Модули](./basics/architecture/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен.
- [Архитектура: Сегменты](./basics/architecture/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
- [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте.
- [Документирование](./basics/documentation.md) — Что и как документировать в коде.
- [Типизация](./basics/typing.md) — Как типизируется код в проекте.
## Создание проекта
- [Из шаблона](./creating-project/from-template.md) — Создание нового проекта на основе готового шаблона.
- [По гайду вручную](./creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона.
- [Чистый Next.js](./creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
## Работа с данными
- [Введение](./data/index.md) — Какие источники данных используются в проекте и как с ними работать.
- [REST](./data/rest/index.md) — Как правильно работать с REST API в проекте.
- [REST: Создание клиента](./data/rest/clients/index.md) — Как выбрать способ создания REST-клиента и где размещать его части.
- [REST: Создание клиента: Автогенерация из OpenAPI](./data/rest/clients/auto.md) — Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
- [REST: Создание клиента: Ручное создание](./data/rest/clients/manual.md) — Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
- [REST: Создание клиента: GET-хуки REST-клиента](./data/rest/clients/hooks.md) — Прозрачные SWR-обёртки над GET-методами REST-клиента.
- [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-данными от сервера: подписки и события.
## Прикладные разделы
- [Структура проекта](./applied/project-structure.md) — Из чего состоит проект и где что лежит.
- [Страницы](./applied/page-level.md) — Как работать со страницами и другими файлами роутинга Next.js App Router.
- [Компонент](./applied/component.md) — Как создавать React-компоненты внутри SLM-модулей.
- [Модуль](./applied/module.md) — Как создавать и организовывать SLM-модули в проекте.
- [Стили: Настройка](./applied/styles/styles-setup.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
- [Стили: Использование](./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-иконки в коде.
- [Изображения](./applied/images.md) — Как подключать изображения через Next.js Image в проекте.
- [Шрифты](./applied/fonts.md) — Как подключать шрифты через Next.js Font в проекте.
- [Алиасы импортов](./applied/aliases.md) — Какие алиасы импортов есть в проекте и как ими пользоваться.
- [Шаблоны генерации](./applied/templates/templates-intro.md) — Что такое шаблоны кодогенерации и какие проблемы они решают.
- [Шаблоны генерации: Настройка](./applied/templates/templates-setup.md) — Первичная установка шаблонов кодогенерации в проект.
- [Шаблоны генерации: Создание шаблонов](./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

@@ -1,77 +0,0 @@
---
title: Алиасы импортов
description: Какие алиасы импортов есть в проекте и как ими пользоваться.
keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infrastructure, ui, shared]
---
# Алиасы импортов
Какие алиасы импортов есть в проекте и как ими пользоваться.
## Конфиг
`tsconfig.json` в корне проекта:
```json
{
"compilerOptions": {
"paths": {
"app/*": ["./src/app/*"],
"layouts/*": ["./src/layouts/*"],
"screens/*": ["./src/screens/*"],
"widgets/*": ["./src/widgets/*"],
"business/*": ["./src/business/*"],
"infrastructure/*": ["./src/infrastructure/*"],
"ui/*": ["./src/ui/*"],
"shared/*": ["./src/shared/*"]
}
}
}
```
Восемь алиасов — ровно по числу слоёв. Других алиасов в проекте нет.
## Правила
- **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля.
- **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля.
- **Префикс `@/` не используется.** Имя слоя — само по себе адрес.
- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](/docs/basics/architecture/layers)).
**Хорошо**
```ts
import { Button } from 'ui/button'
import { useUser } from 'business/user'
import { formatDate } from 'shared/utils/date'
```
**Плохо**
```ts
// Относительный путь между модулями
import { Button } from '../../../ui/button'
// Префикс @/, которого нет в paths
import { Button } from '@/ui/button'
// Алиас на src — не предусмотрен
import { Button } from 'src/ui/button'
```
## Внутри модуля
Внутри своего модуля — относительные пути:
```ts
// src/ui/button/button.tsx
import styles from './button.module.css'
import { Icon } from './icon'
```
Не использовать алиас на самого себя:
```ts
// Плохо — алиас вместо относительного пути внутри модуля
import { Icon } from 'ui/button/icon'
```

View File

@@ -1,81 +0,0 @@
---
title: Biome
description: Установка и настройка линтера-форматтера в новом проекте.
keywords: [biome, линтер, форматтер, lint, format, biome.json, "@biomejs/biome", замена eslint, замена prettier]
---
# Biome
Установка и настройка линтера-форматтера в новом проекте.
## Требования
- Node.js 18+.
- Проект без установленного ESLint и Prettier (они конфликтуют с Biome).
## Установка
1. Установить пакет:
```bash
npm install --save-dev --save-exact @biomejs/biome
```
2. Инициализировать конфиг:
```bash
npx @biomejs/biome init
```
В корне появится `biome.json` с дефолтными настройками.
3. Привести `biome.json` к стандартному виду — добавить override для `*.css` (см. «Стандартный `biome.json`»). Делается сразу после `init`, до первого запуска `lint`/`check`.
4. Добавить скрипты в `package.json`:
```json
{
"scripts": {
"lint": "biome lint .",
"format": "biome format --write .",
"check": "biome check --write ."
}
}
```
| Скрипт | Что делает |
|--------|-----------|
| `lint` | Проверка правил без правок |
| `format` | Автоформатирование всех файлов |
| `check` | Lint + format + organize imports в один проход (основная команда) |
## Стандартный `biome.json`
Дефолтный `biome.json`, созданный `biome init`, кастомизируется ровно одним блоком — `overrides` для `*.css` с отключённым правилом `suspicious/noUnknownAtRules`. Этот override **обязателен по умолчанию во всех проектах**, независимо от того, подключены ли уже стили: проектный CSS-стек использует `@custom-media` и другие нестандартные at-правила, которые Biome не распознаёт; без override `npm run lint` падает.
Фрагмент, который добавляется в `biome.json`:
```jsonc
{
"overrides": [
{
"includes": ["**/*.css"],
"linter": {
"rules": {
"suspicious": {
"noUnknownAtRules": "off"
}
}
}
}
]
}
```
Если в `biome.json` уже есть массив `overrides` — добавить элемент в него; не дублировать массив.
Прочая настройка правил Biome — отдельная задача, не входит в стандартный канон.
## Интеграция с VS Code
Расширение `biomejs.biome` и автоформатирование при сохранении настраиваются в [VS Code](/docs/applied/vscode).

View File

@@ -1,165 +0,0 @@
---
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'
```

View File

@@ -1,128 +0,0 @@
---
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

@@ -1,95 +0,0 @@
---
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

@@ -1,81 +0,0 @@
---
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-модуля.

View File

@@ -1,156 +0,0 @@
---
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

@@ -1,186 +0,0 @@
---
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

@@ -1,70 +0,0 @@
---
title: PostCSS
description: Установка и настройка CSS-процессора в новом проекте.
keywords: [postcss, postcss.config.mjs, postcss-custom-media, postcss-nesting, autoprefixer, postcss-global-data, csstools, "@custom-media", "@nest", css-процессор]
---
# PostCSS
Установка и настройка CSS-процессора в новом проекте.
## Зачем PostCSS
Подключаем ради двух вещей:
- **Вложенность** — `&:hover`, `&::before`, `&._active` и `@media` внутри селектора. Без процессора нативный CSS не покрывает всех нужных кейсов вложенности.
- **`@custom-media`** — единые breakpoints проекта (`@media (--md)`) вместо магических `min-width`. Определяются в одном месте, переиспользуются везде.
Autoprefixer и `@csstools/postcss-global-data` идут довеском под эти две задачи.
## Требования
- Next.js 14+ (App Router).
- Node.js 18+.
CSS Modules поддерживаются Next.js из коробки — отдельной установки не требуют.
## Установка
1. Установить PostCSS-плагины как devDependencies:
```bash
npm install -D postcss-custom-media postcss-nesting autoprefixer @csstools/postcss-global-data
```
2. Создать `postcss.config.mjs` в корне проекта (см. «Конфиг»).
## Конфиг
Файл `postcss.config.mjs` в корне проекта.
```js
// postcss.config.mjs
export default {
plugins: {
'@csstools/postcss-global-data': {
files: ['src/shared/styles/media.css'],
},
'postcss-custom-media': {},
'postcss-nesting': {},
autoprefixer: {},
},
}
```
### Разбор плагинов
| Плагин | Назначение |
|--------|------------|
| `@csstools/postcss-global-data` | Подгружает определения `@custom-media` из `src/shared/styles/media.css` перед обработкой каждого CSS-модуля. Семантика — «глобальный файл определений, который не импортируется в исходники» |
| `postcss-custom-media` | Поддержка `@custom-media --md (...)` и использования `@media (--md) {}`. Определения берутся из файла, который подгрузил `postcss-global-data` |
| `postcss-nesting` | Нативная CSS-вложенность: `&:hover`, `&::before`, `&._active` |
| `autoprefixer` | Добавление вендорных префиксов по browserslist |
### Почему внешний файл с `@custom-media`, а не `@import`
`@custom-media` — глобальные определения, одинаковые для всего проекта. Держим их в `src/shared/styles/media.css`. `@csstools/postcss-global-data` подгружает этот файл перед каждым модулем, а `postcss-custom-media` заменяет `@media (--md)` на конкретные `@media (min-width: ...)` на этапе сборки. Сами определения в бандл не попадают.
Опция `importFrom` у `postcss-custom-media` удалена в v10+; её роль теперь выполняет `@csstools/postcss-global-data`.
Импортировать `media.css` в файлы компонентов **не нужно** и запрещено правилами (см. [Использование стилей](/docs/applied/styles/styles-usage), раздел «Импорт стилей»).

View File

@@ -1,176 +0,0 @@
---
title: Настройка стилей
description: "Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили."
keywords: [variables.css, media.css, global.css, shared/styles, токены, переменные, custom-media, breakpoints, подключение стилей, базовые стили, инициализация]
---
# Настройка стилей
Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
## Требования
- Установлен PostCSS или любой другой pre/post-процессор с поддержкой `@custom-media`.
## Файлы
Состав глобальных стилей — три файла:
| Файл | Роль |
|------|------|
| `variables.css` | Токены проекта (цвета, отступы, радиусы) |
| `media.css` | Custom media queries (брейкпоинты по ширине и высоте) |
| `global.css` | Точка сборки глобальных стилей: через `@import` тянет все остальные глобалы, импортируется в приложение один раз |
Правила подключения:
- В приложение импортируется **только** `global.css`.
- `variables.css` и будущие глобальные файлы (резеты, темы, типографика) подключаются в `global.css` через `@import`.
- `media.css` **не импортируется** — ни в `global.css`, ни в компоненты, ни в точку инициализации. Его читает CSS-процессор на этапе сборки (см. [PostCSS](/docs/applied/postcss)).
## Корневой `font-size`
Базовая единица `rem` в проекте привязана к **16px**: корневой `font-size` не переопределяется. `html { font-size: ... }` писать запрещено — пользовательская настройка размера шрифта в браузере должна работать (a11y). Все `rem`-значения в `media.css` и других стилях трактуются как `1rem = 16px по умолчанию`.
Reset браузерных дефолтов (`box-sizing`, сброс `margin`, типографика) каноном не задаётся — каждый проект решает сам. Если заводится — подключается через `global.css`.
## Установка
### 1. Создать файлы
```bash
mkdir -p src/shared/styles
touch src/shared/styles/variables.css src/shared/styles/media.css src/shared/styles/global.css
```
### 2. Заполнить `media.css`
Файл `src/shared/styles/media.css`. Стандартный набор брейкпоинтов проекта; редактировать только при согласованном изменении шкалы.
Единица — `rem` (реагирует на корневой `font-size`). Перевод исходит из дефолтного `html { font-size: 16px }`, т.е. `1rem = 16px`.
```css
/* src/shared/styles/media.css */
/* Ширина — Mobile First (min-width), кроме --xs (max-width) */
@custom-media --xs (max-width: 35.9375rem); /* 575px — до sm */
@custom-media --sm (min-width: 36rem); /* 576px — телефон альбом / малый планшет */
@custom-media --md (min-width: 48rem); /* 768px — планшет */
@custom-media --lg (min-width: 62rem); /* 992px — малый десктоп */
@custom-media --xl (min-width: 75rem); /* 1200px — десктоп */
@custom-media --2xl (min-width: 88rem); /* 1408px — широкий десктоп */
@custom-media --3xl (min-width: 120rem); /* 1920px — full HD+ */
/* Высота — min-height */
@custom-media --h-xs (min-height: 41.6875rem); /* 667px — iPhone SE портрет */
@custom-media --h-sm (min-height: 43.875rem); /* 702px */
@custom-media --h-md (min-height: 50.625rem); /* 810px — iPad портрет */
@custom-media --h-lg (min-height: 56.25rem); /* 900px */
@custom-media --h-xl (min-height: 62.5rem); /* 1000px */
@custom-media --h-2xl (min-height: 68.75rem); /* 1100px */
@custom-media --h-3xl (min-height: 75rem); /* 1200px */
```
Правила:
- только `@custom-media` на верхнем уровне;
- имена короткие, по шкале (`--xs``--3xl`); высотные — с префиксом `--h-`;
- единица — `rem`, не `em`/`px`; пиксельное значение указывается комментарием;
- значения ширины — `min-width` (Mobile First), исключение `--xs``max-width` (блок «строго меньше `--sm`»);
- значения высоты — `min-height`.
### 3. Заполнить `variables.css`
Файл `src/shared/styles/variables.css`. Набор токенов под проект расширяется по мере роста дизайн-системы.
```css
/* src/shared/styles/variables.css */
:root {
/* Цвета */
--color-primary: #3b82f6;
--color-bg: #ffffff;
--color-bg-hover: #f5f5f5;
--color-text: #1a1a1a;
/* Отступы */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
/* Скругления */
--radius-1: 4px;
--radius-2: 8px;
}
```
Правила:
- все токены определяются в `:root` — без вложенных селекторов;
- именование — `kebab-case` по ролям: `--color-*`, `--space-*`, `--radius-*`;
- `px` — основная единица для пространственных токенов;
- темы накладываются поверх через `[data-theme="..."] { ... }` — в отдельном файле темы или здесь же.
`variables.css` напрямую в приложение не импортируется — только через `global.css`.
### 4. Заполнить `global.css`
Файл `src/shared/styles/global.css`. Единственный глобальный файл, импортируемый в точку инициализации приложения. Внутри — `@import` остальных глобалов относительным путём.
```css
/* src/shared/styles/global.css */
@import './variables.css';
/* Сюда же подключаются будущие глобалы через @import:
* @import './reset.css';
* @import './typography.css';
* @import './themes.css';
* media.css НЕ импортируется — он работает через PostCSS.
*/
```
Правила:
- пути в `@import` — относительные (`./variables.css`), не через алиасы; нативный CSS `@import` не понимает tsconfig-paths;
- `media.css` в `global.css` **не импортируется**;
- собственные глобальные правила (`html { ... }`, `body { ... }`) писать **не здесь**, а в отдельных файлах рядом (`reset.css`, `typography.css`) и подключать через `@import`. `global.css` — только точка сборки;
- порядок `@import` определяет порядок каскада: токены первыми, дальше резеты / темы / типографика.
### 5. Подключить `global.css` в layout
Импорт делается **один раз** — в корневом 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">
<body>{children}</body>
</html>
)
}
```
`variables.css` и `media.css` в layout **не импортируются напрямую** — только через `global.css` (variables) или через PostCSS на сборке (media).
## Проверка установки
- В `src/shared/styles/` присутствуют три файла: `variables.css`, `media.css`, `global.css`. В `src/app/` папки `styles/` нет.
- В `src/app/layout.tsx` есть `import 'shared/styles/global.css'`. Импортов `variables.css` и `media.css` там нет.
- В проекте **не появились** PostCSS-пакеты и `postcss.config.*` — этот раздел их не ставит.
- `npm run build` завершается успешно.
## Дальше
- [PostCSS](/docs/applied/postcss) — подключить процессор, чтобы заработали `@media (--md)` и вложенность.
- [Использование стилей](/docs/applied/styles/styles-usage) — правила написания CSS в компонентах.
- [SVG-спрайты](/docs/applied/svg-sprites/svg-sprites-setup) — стили иконок отдельно от глобальных.

View File

@@ -1,31 +0,0 @@
---
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,132 +0,0 @@
---
title: Настройка SVG-спрайтов
description: Подключение SVG-спрайтов в новом проекте.
keywords: [svg-sprites, установка, настройка, config, пакет, "@gromlab/svg-sprites", svg-sprites.config.ts]
---
# Настройка SVG-спрайтов
Подключение SVG-спрайтов в новом проекте.
## Установка
1. Установить пакет:
```bash
npm install @gromlab/svg-sprites
```
2. Создать `svg-sprites.config.ts` в корне проекта (см. [Стандартный конфиг](#стандартныи-конфиг)).
3. Создать папку входа для SVG-файлов в слое `shared`:
```bash
mkdir -p src/shared/sprites/icons
```
Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` посторонних каталогов вне слоёв не заводим.
4. Добавить скрипты в `package.json`:
```json
{
"scripts": {
"sprite": "svg-sprites",
"predev": "svg-sprites",
"prebuild": "svg-sprites"
}
}
```
Хуки `predev` и `prebuild` гарантируют, что спрайты и типы всегда актуальны перед запуском и сборкой.
5. Добавить сгенерированные артефакты в `.gitignore`:
```text
# Сгенерированные спрайты и React-компонент
/public/sprites/
/src/ui/svg-sprite/
```
6. Выполнить первую генерацию:
```bash
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 конкретной страницы или маршрута.
## Стандартный конфиг
Файл `svg-sprites.config.ts` в корне проекта. Это канон — отклонения только по явной причине.
```ts
// svg-sprites.config.ts
import { defineConfig } from '@gromlab/svg-sprites'
export default defineConfig({
output: 'public/sprites',
publicPath: '/sprites',
react: 'src/ui/svg-sprite',
sprites: [
{ name: 'icons', input: 'src/shared/sprites/icons' },
],
})
```
### Фиксированные значения
| Опция | Значение | Почему так |
|-------|----------|------------|
| `output` | `public/sprites` | Единая папка статики Next.js |
| `publicPath` | `/sprites` | URL-путь без `public/` (Next.js раздаёт `public/` как `/`) |
| `react` | `src/ui/svg-sprite` | Слой `ui/` из архитектуры проекта (→ [Архитектура](/docs/basics/architecture/)) |
| `sprites[0].name` | `icons` | Основной спрайт всегда называется `icons` |
### Трансформации
Все значения по умолчанию оставлять включёнными:
```ts
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
}
```
Явно прописывать блок `transform` не нужно — пакет применяет эти значения по умолчанию.
Отключать `replaceColors` — только для отдельного спрайта с фиксированной палитрой (например, брендовые логотипы). Делать это на уровне спрайта, не глобально.
### Режим
По умолчанию `mode: 'stack'` — не указывать явно. Переход на `symbol` требует обоснования: превью и примеры в пакете оптимизированы под `stack`.
## Дальше
- [Использование](/docs/applied/svg-sprites/svg-sprites-usage) — добавление иконок, компонент `<SvgSprite/>`, управление цветом.

View File

@@ -1,56 +0,0 @@
---
title: Использование SVG-спрайтов
description: Как добавлять и использовать SVG-иконки в коде.
keywords: [svg, спрайт, sprite, иконка, icon, SvgSprite, превью, preview, цвет, color]
---
# Использование SVG-спрайтов
Как добавлять и использовать SVG-иконки в коде.
## Шаги
1. **Положить SVG в папку спрайта:**
```text
src/shared/sprites/icons/new-icon.svg
```
2. **Импортировать компонент.** Компонент `<SvgSprite/>` генерируется пакетом вместе с типами имён иконок — автодополнение работает без ручных описаний:
```tsx
import { SvgSprite } from 'ui/svg-sprite'
<SvgSprite icon="new-icon" />
```
3. **Посмотреть и пощупать иконку — в превью.** Пакет генерирует HTML-превью рядом со спрайтом (`public/sprites/icons.preview.html`). Там виден набор иконок, имена и поведение цвета.
## Управление цветом
При сборке цвета в SVG заменяются на CSS-переменные `--icon-color-N`. Управление — через обычный CSS родителя.
**Моно-иконка** наследует `color` родителя (`--icon-color-1` по умолчанию `currentColor`):
```css
.button {
color: var(--color-primary);
}
```
**Точечное переопределение** — через переменную:
```css
.icon-danger {
--icon-color-1: var(--color-danger);
}
```
**Мульти-иконка** — переменные задаются явно, порядок виден в превью:
```css
.folder {
--icon-color-1: var(--color-folder-bg);
--icon-color-2: var(--color-folder-accent);
}
```

View File

@@ -1,97 +0,0 @@
---
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

@@ -1,32 +0,0 @@
---
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

@@ -1,44 +0,0 @@
---
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

@@ -1,45 +0,0 @@
---
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,108 +0,0 @@
# SLM Design
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
::: warning Локальная копия
Документация по архитектуре — локальная копия. Оригинал находится на сайте [slm-design.gromlab.ru](https://slm-design.gromlab.ru/).
:::
## Разделы спецификации
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
## Преимущества
### Вертикальная организация домена
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
### Dependency Injection без фреймворков
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
### Разделение ответственности без перегрузки слоёв
Сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
### Горизонтальная инкапсуляция
Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
### Колокация по умолчанию
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
### Явное разделение каркаса и контента
Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью.
### Масштабирование через группировку
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
## Происхождение
SLM Design вырос на основе:
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
- **Colocation Principle** — код живёт рядом с местом использования
## Пример структуры проекта
```text
src/
├── app/
├── layouts/
│ ├── main/
│ └── dashboard/
├── screens/
│ ├── home/
│ ├── products/
│ ├── product-detail/
│ └── about/
├── widgets/
│ ├── page-heading/
│ ├── hero-section/
│ └── promo-banner/
├── business/
│ ├── auth/
│ ├── catalog/
│ ├── orders/
│ └── chat/
├── infra/
│ ├── theme/
│ ├── i18n/
│ ├── backend-api/
│ └── logger/
├── ui/
│ ├── button/
│ ├── input/
│ ├── modal/
│ ├── toast/
│ └── dropdown/
└── shared/
├── lib/
├── types/
└── styles/
```
## Принципы
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.

View File

@@ -1,249 +0,0 @@
# Слои
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
## Определение
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
## Группы слоёв
Слои делятся на три группы:
| Группа | Слои | Описание |
|--------|------|----------|
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
| Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
## Направление зависимостей
Любой импорт между модулями — только через публичный API.
```
app → [ layouts | screens ] → widgets → business → infra → ui → shared
```
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
- Модули одного слоя в группе «Композиция» изолированы друг от друга
- Модули одного слоя `infra` и `ui` могут импортировать друг друга через публичный API
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
## Слой App
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
### Требования
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
- Никем не импортируется
## Слой Layouts
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
```text
src/layouts/
├── main/
├── dashboard/
└── auth/
```
### Требования
- Содержит только модули
- Не содержит бизнес-логику
- Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую
## Слой Screens
Контент конкретной страницы: собирает её из модулей нижних слоёв.
```text
src/screens/
├── home/
├── products/
├── product-detail/
├── about/
└── contacts/
```
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`).
```text
src/screens/
├── shop/
│ ├── home/
│ ├── products/
│ ├── product-detail/
│ └── cart/
├── account/
│ ├── profile/
│ ├── settings/
│ └── order-history/
└── info/
├── about/
├── contacts/
└── faq/
```
### Требования
- Содержит только модули
- Не содержит бизнес-логику
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business`
## Слой Widgets
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget.
```text
src/widgets/
├── page-heading/
├── hero-section/
├── onboarding-checklist/
├── promo-banner/
└── error-boundary/
```
### Требования
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/`
- Используется в нескольких screens или layouts
## Слой Business
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный runtime API через фабрику в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `import type`.
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
```text
src/business/
├── auth/
├── catalog/
├── orders/
├── checkout/
└── chat/
```
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`).
```text
src/business/
├── commerce/
│ ├── catalog/
│ ├── cart/
│ ├── orders/
│ └── checkout/
└── communication/
├── chat/
└── notifications/
```
### Требования
- Один модуль = один бизнес-домен
- Циклические зависимости между доменами запрещены
- Публичный runtime API — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты
- Импорт runtime-кода между доменами — через фабрику. `import type` — напрямую
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
## Слой infra
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`.
Отличие от `shared/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
```text
src/infra/
├── theme/
├── i18n/
├── backend-api/
├── maps-api/
├── logger/
├── feature-flags/
└── realtime/
```
### Требования
- Один модуль = один техсервис
- Импортирует `infra/`, `ui/`, `shared/`
## Слой UI
UI-кит без бизнес-логики: button, carousel, toast, modal.
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
```text
src/ui/
├── button/
├── input/
├── icon/
├── carousel/
├── modal/
├── toast/
├── dropdown/
├── tabs/
└── tooltip/
```
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
```text
src/ui/
├── primitives/
│ ├── button/
│ ├── input/
│ ├── icon/
│ └── badge/
└── composites/
├── carousel/
├── modal/
├── dropdown/
├── tabs/
└── tooltip/
```
### Требования
- Не содержит бизнес-логику
- Импортирует только `ui/` и `shared/`
## Слой Shared
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
Отличие от `infra/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
```text
src/shared/
├── lib/
├── types/
├── styles/
└── sprites/
```
### Требования
- Не имеет runtime-состояния

View File

@@ -1,284 +0,0 @@
# Модули
Раздел описывает модуль как границу ответственности в 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,176 +0,0 @@
# Сегменты
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
## Определение
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
## Обзор
| Сегмент | Содержимое |
|---------|------------|
| `ui/` | Презентационные компоненты родительского модуля |
| `parts/` | Вложенные модули со своими сегментами |
| `hooks/` | React-хуки |
| `stores/` | Сторы состояния |
| `services/` | Работа с внешними источниками данных |
| `mappers/` | Трансформация данных между форматами |
| `types/` | TypeScript-типы и интерфейсы |
| `styles/` | Стили |
| `lib/` | Утилиты и хелперы модуля |
| `config/` | Константы и конфигурация |
## Сегмент ui/
Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля.
Компонент в `ui/`:
- Находится в собственной папке.
- Может содержать только `{name}.tsx`, `index.ts`, `styles/`, `types/`.
- Не содержит любые компоненты.
- Не содержит любые модули.
- Не импортирует код проекта за пределами родительского модуля.
- Не делает внешние запросы.
- Не вызывает сценарные хуки.
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
- Не содержит бизнес-логику или сценарную логику.
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент).
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
```text
user/
├── ui/
│ ├── user-avatar/
│ │ ├── user-avatar.tsx
│ │ ├── styles/
│ │ │ └── user-avatar.module.css
│ │ ├── types/
│ │ │ └── user-avatar-props.type.ts
│ │ └── index.ts
│ └── user-status/
│ ├── user-status.tsx
│ └── index.ts
├── types/
├── hooks/
├── user.tsx
└── index.ts
```
Если UI-сущности нужна внутренняя декомпозиция, сценарная логика, получение данных или собственные архитектурные зависимости — это уже не компонент в `ui/`, а модуль в `parts/`.
## Сегмент parts/
Вложенные модули со своими сегментами. `parts/` содержит только модули: каждый элемент `parts/` — папка полноценного модуля с собственным публичным API. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не размещаются.
```text
home/
├── parts/
│ ├── hero-section/
│ │ ├── hero-section.tsx
│ │ ├── styles/
│ │ ├── parts/
│ │ │ └── top-banner/
│ │ │ ├── top-banner.tsx
│ │ │ └── index.ts
│ │ └── index.ts
│ └── features-section/
│ ├── features-section.tsx
│ ├── hooks/
│ └── index.ts
├── home.screen.tsx
└── index.ts
```
Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — компонент родительского модуля без собственной архитектурной ответственности.
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
## Сегмент hooks/
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
```text
hooks/
├── use-auth.hook.ts
├── use-session.hook.ts
└── use-permissions.hook.ts
```
## Сегмент stores/
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
```text
stores/
├── auth.store.ts
└── session.store.ts
```
## Сегмент services/
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
```text
services/
├── auth.service.ts
└── token.service.ts
```
## Сегмент mappers/
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
```text
mappers/
├── map-user.ts
├── map-product.ts
└── map-order-to-dto.ts
```
## Сегмент types/
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
```text
types/
├── user.type.ts
└── session.type.ts
```
## Сегмент styles/
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
```text
styles/
├── auth.module.css
└── login-form.module.css
```
## Сегмент lib/
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
```text
lib/
├── validate-email.ts
└── format-phone.ts
```
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
## Сегмент config/
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
```text
config/
├── routes.ts
└── constants.ts
```

View File

@@ -1,51 +0,0 @@
---
title: Создание проекта из шаблона
description: Создание нового проекта на основе готового шаблона.
keywords: [создать проект из шаблона, шаблон, template, tiged, degit, клонировать шаблон, эталонный шаблон, быстрый старт, scaffold, новый проект]
---
# Создание проекта из шаблона
Создание нового проекта на основе готового шаблона.
## Что внутри
Шаблон — готовый скелет проекта с применёнными правилами стайлгайда:
- **Стек:** Next.js (App Router), TypeScript, React.
- **Архитектура:** структура папок по SLM, алиасы импортов.
- **Качество кода:** Biome (линтер и форматер), настройки VS Code.
- **Стили:** PostCSS Modules с плагинами, токены, медиа-брейкпоинты.
- **Ассеты:** генерация SVG-спрайтов.
- **Кодогенерация:** шаблоны для страниц, компонентов, хуков, сторов.
в
## Установка
1. Склонировать шаблон в родительском каталоге будущего проекта:
```bash
npx tiged git@gromlab.ru:templates/nextjs.git my-app
```
`tiged` копирует снимок репозитория без истории git. Имя каталога (`my-app`) заменяется на нужное.
2. Установить зависимости:
```bash
cd my-app
npm install
```
3. Проверить сборку:
```bash
npm run build
```
Сборка должна завершиться без ошибок.
## Правила
- **Шаблон — источник истины.** Не добавлять, не удалять и не переименовывать файлы шаблона «для приведения к канону»: шаблон уже канонический. Любое несоответствие — баг шаблона, а не проекта.
- **Менеджер пакетов — npm.** Отклонение (pnpm, yarn, bun) — только по явному решению с пониманием, что стайлгайд этого не предусматривает.
- **Не инициализировать git заново** автоматически. `tiged` намеренно не создаёт `.git/` — решение о репозитории принимает разработчик.

View File

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

View File

@@ -1,112 +0,0 @@
---
title: Чистая установка Next.js
description: "Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку."
keywords: [next.js, create-next-app, npx, установка, инициализация, фреймворк, скаффолдинг, app router, typescript]
---
# Чистая установка Next.js
Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
## Требования
- Node.js 18.18+ (рекомендуется LTS 20+).
- npm 10+.
- Рабочая папка пуста, либо для установки выбрана подпапка (`create-next-app@16+` отказывается ставиться в непустую директорию).
## Установка
### 1. Инициализация через `create-next-app`
Флаги зафиксированы и не согласовываются — это канон стайлгайда:
```bash
npx create-next-app@latest my-app \
--typescript \
--app \
--src-dir \
--import-alias "@/*" \
--no-eslint \
--no-tailwind \
--use-npm
```
| Флаг | Значение | Почему так |
|------|----------|------------|
| `--typescript` | TS включён | Стайлгайд требует TypeScript ([Типизация](/docs/basics/typing)) |
| `--app` | App Router | Pages Router не используется |
| `--src-dir` | Код в `src/` | Архитектура SLM требует `src/` ([Структура проекта](/docs/applied/project-structure)) |
| `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](/docs/applied/aliases)) |
| `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](/docs/applied/biome)) |
| `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](/docs/applied/styles/styles-usage)) |
| `--use-npm` | Пакетный менеджер — npm | Единый инструмент в проектах |
### 2. Очистить дефолтный шаблон
`create-next-app` генерирует демо-страницу со стилями и ассетами, а Next.js 16+ дополнительно кладёт в корень собственные `AGENTS.md` и `CLAUDE.md` — всё это удаляется.
```bash
rm src/app/page.module.css
rm src/app/globals.css
rm public/next.svg public/vercel.svg public/file.svg public/globe.svg public/window.svg
rm -f AGENTS.md CLAUDE.md
```
Заменить `src/app/page.tsx` на минимальный:
```tsx
// src/app/page.tsx
export default function HomePage() {
return <h1>Home</h1>
}
```
Очистить `src/app/layout.tsx` от импорта шрифтов и `globals.css`:
```tsx
// src/app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'App',
description: '',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ru">
<body>{children}</body>
</html>
)
}
```
### 3. Создать папку `src/shared/styles/`
Глобальные стили в SLM-архитектуре живут в слое `shared`, а не в `src/app/` ([Структура проекта](/docs/applied/project-structure)).
```bash
mkdir -p src/shared/styles
```
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
## Правила
- **Конфликт с непустой директорией** — не удалять файлы пользователя автоматически. Ставить в подпапку или временно перенести посторонние файлы.
- **Отклонение от канонических флагов** (pnpm, Tailwind, ESLint и т.п.) — только осознанное, с пониманием, что стайлгайд этого не предусматривает.
- **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](/docs/applied/aliases)).
- **`AGENTS.md` от Next.js** удаляется в шаге 2. Повторно не создаётся.
- **Biome, стили, PostCSS, SVG-спрайты, VS Code** — отдельные шаги установки, не в этом разделе.
## Проверка установки
- В корне проекта: `next.config.ts`, `tsconfig.json`, `package.json`.
- В `package.json`: Next.js установлен, нет `eslint`, `tailwindcss`.
- В `src/app/` присутствуют минимальные `page.tsx` и `layout.tsx`. `globals.css`, `page.module.css` отсутствуют. Каталогов `styles/`, `assets/`, `providers/`, `components/` в `src/app/` нет.
- Папка `src/shared/styles/` создана (пустая).
- В `src/` из слоёв SLM присутствуют только `app/` и `shared/` (с `styles/`). Посторонних каталогов нет.
- В `public/` удалены `next.svg`, `vercel.svg`, `file.svg`, `globe.svg`, `window.svg`.
- В корне проекта нет `AGENTS.md` и `CLAUDE.md` от Next.js.
- `npm run build` завершается успешно.
- Пакетный менеджер — npm (нет `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`).

View File

@@ -1,60 +0,0 @@
---
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

@@ -1,79 +0,0 @@
---
title: Realtime
description: "Работа с push-данными от сервера: подписки и события."
keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRSubscription, push, события]
---
# Realtime
Работа с push-данными от сервера: подписки и события.
## Принципы
- **Клиент realtime — в `infrastructure/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
- **Использование на клиенте — два сценария:**
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
- **Прямая подписка** — для побочных эффектов (тосты, нотификации, аналитика), не привязанных к рендеру.
## Размещение клиента
```text
src/infrastructure/
└── {channel-name}/
├── connection.ts # установление соединения, реконнект
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
├── types.ts
└── index.ts
```
## Использование через SWR
```tsx
'use client'
import useSWRSubscription from 'swr/subscription'
import { subscribe } from 'infrastructure/notifications'
export function NotificationCounter() {
const { data: count } = useSWRSubscription(
['notifications', 'count'],
(key, { next }) =>
subscribe('notifications.count', (value: number) => next(null, value)),
)
return <span>{count ?? 0}</span>
}
```
Плюсы: кеш и дедупликация подписки между несколькими местами рендера; единая модель данных с REST.
## Прямая подписка
Для побочных эффектов, которые не влияют на состояние UI напрямую:
```tsx
'use client'
import { useEffect } from 'react'
import { subscribe } from 'infrastructure/notifications'
import { showToast } from 'ui/toast'
export function NotificationsToaster() {
useEffect(() => {
return subscribe('notifications.new', (notification) => {
showToast(notification.message)
})
}, [])
return null
}
```
Возврат `unsubscribe` из `useEffect` обязателен — иначе утечка подписки.
## Запрет прямых соединений
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infrastructure/`.
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.

View File

@@ -1,193 +0,0 @@
---
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

@@ -1,206 +0,0 @@
---
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

@@ -1,75 +0,0 @@
---
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

@@ -1,187 +0,0 @@
---
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

@@ -1,74 +0,0 @@
---
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

@@ -1,121 +0,0 @@
---
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

@@ -1,89 +0,0 @@
---
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

@@ -1,109 +0,0 @@
---
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

@@ -1,100 +0,0 @@
---
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

@@ -1,82 +0,0 @@
---
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

@@ -1,62 +0,0 @@
---
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

@@ -1,88 +0,0 @@
---
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,97 +0,0 @@
---
title: NextJS Style Guide
description: Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
---
# NextJS Style Guide
Стандарты разработки фронтенд-приложений на Next.js и TypeScript.
## Использование
**Для AI-агентов:**
- [llms.txt](/llms.txt) — Карта разделов, оглавление со ссылками на разделы.
- [llms-full.txt](/llms-full.txt) — Вся документация одним файлом.
**Для проекта:**
- [nextjs-style-guide.zip](/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
## Структура документации
### Подсказки
[Подсказки](/docs/workflow) — короткие ответы на типовые вопросы и решения для спорных ситуаций.
### Базовые правила
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Технологии и библиотеки | Какой стек используем? |
| Именование | Как называть файлы, переменные, компоненты, хуки? |
| SLM Design | Что такое SLM и зачем она нужна? |
| Архитектура: Слои | Какие слои есть и как между ними устроены зависимости? |
| Архитектура: Модули | Что такое модуль и как он устроен? |
| Архитектура: Сегменты | Какие сегменты есть внутри модуля? |
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
| Документирование | Как писать JSDoc: что документировать, а что нет? |
| Типизация | Как типизировать: type vs interface, any/unknown? |
### Создание проекта
**Как начать новый проект** — варианты установки и эталонный набор инструментов.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Создание проекта из шаблона | Как начать проект из готового шаблона? |
| Создание проекта вручную | Как поднять проект с нуля без шаблона? |
| Чистая установка Next.js | Как поставить голый Next.js под дальнейшую сборку? |
### Настройка
**Как сконфигурировать проект** — пошаговая настройка инструментов и инфраструктуры.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Алиасы импортов | Как настроить алиасы импортов? |
| Biome | Как настроить линтер и форматтер? |
| PostCSS | Какие плагины PostCSS нужны и как их настроить? |
| Стили | Как подключить базовые стили и токены? |
| SVG-спрайты | Как подключить генерацию SVG-спрайтов? |
| Шаблоны генерации | Как подключить шаблоны для кодогенерации? |
| VS Code | Как настроить редактор для проекта? |
### Использование
**Как это устроено и как этим пользоваться** — структура, примеры и правила для конкретных областей.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Структура проекта | Как организованы папки и файлы по SLM? |
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
| Файлы роутинга | Как описывать layout, page, loading, error, not-found? |
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
| Стили | Как писать CSS: вложенность, медиа, токены? |
| SVG-спрайты | Как использовать SVG-спрайты в коде? |
| Изображения | _(не заполнен)_ |
| Видео | _(не заполнен)_ |
| Stores | _(не заполнен)_ |
| Хуки | _(не заполнен)_ |
| Шрифты | _(не заполнен)_ |
| Локализация | _(не заполнен)_ |
### Данные
**Как работать с источниками данных** — REST, realtime и потребление в компонентах.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Источники данных | Как устроена работа с данными в проекте? |
| REST: Автоматическая генерация | Как сгенерировать REST-клиент автоматически из OpenAPI? |
| REST: Ручное создание | Как написать REST-клиент вручную? |
| REST: Серверные компоненты | Как получать данные в серверных компонентах? |
| REST: Клиентские компоненты | Как получать данные в клиентских компонентах? |
| Realtime | Как работать с realtime-каналами и сокетами? |

View File

@@ -1,8 +0,0 @@
---
title: Подсказки
description: Короткие ответы на типовые вопросы и решения для спорных ситуаций.
---
# Подсказки
Короткие ответы на типовые вопросы и решения для спорных ситуаций.

View File

@@ -1,371 +0,0 @@
---
layout: false
---
<script setup>
import { ref, onMounted } from 'vue'
const THEME_KEY = 'vitepress-theme-appearance'
// __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION`
// (см. .vitepress/config.ts). В dev и build всегда определена.
const buildVersion = __BUILD_VERSION__
const theme = ref('auto')
onMounted(() => {
const savedTheme = localStorage.getItem(THEME_KEY)
theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto'
})
function setTheme(value) {
theme.value = value
if (value === 'auto') {
localStorage.removeItem(THEME_KEY)
} else {
localStorage.setItem(THEME_KEY, value)
}
const isDark = value === 'dark' || (value === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', isDark)
}
/**
* Клик по кнопке темы:
* - по активной → переключение в auto;
* - по неактивной → выбор этого варианта.
*/
function toggleTheme(value) {
setTheme(theme.value === value ? 'auto' : value)
}
</script>
<div class="landing">
<section class="landing__hero">
<h1 class="landing__title">NextJS Style Guide</h1>
<ClientOnly>
<p class="landing__tagline">Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.</p>
<div class="landing__controls">
<a
class="landing__repo"
href="https://gromlab.ru/docs/nextjs-style-guide"
target="_blank"
rel="noopener"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.08 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"/>
</svg>
<span>Репозиторий</span>
</a>
<div class="seg seg--icons" role="group" aria-label="Тема">
<button
type="button"
class="seg__btn"
:class="{ 'seg__btn--active': theme === 'light' }"
:aria-pressed="theme === 'light'"
title="Светлая"
@click="toggleTheme('light')"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
</button>
<button
type="button"
class="seg__btn"
:class="{ 'seg__btn--active': theme === 'dark' }"
:aria-pressed="theme === 'dark'"
title="Тёмная"
@click="toggleTheme('dark')"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</button>
</div>
</div>
</ClientOnly>
</section>
<section class="landing__cards">
<a class="landing__card" href="/docs/">
<h3>Документация</h3>
<p>Все разделы: процессы разработки, базовые правила, прикладные руководства.</p>
<span class="landing__cta">Открыть →</span>
</a>
<div class="landing__card landing__card--multi">
<h3>Ассистенту</h3>
<p>
Карта документации для AI-агентов:
<code>/llms.txt</code>, <code>/llms-full.txt</code>.
</p>
<div class="landing__buttons">
<a class="landing__button" href="/llms.txt">llms.txt</a>
<a class="landing__button" href="/llms-full.txt">llms-full.txt</a>
</div>
</div>
<a class="landing__card" href="/nextjs-style-guide.zip">
<h3>Скачать правила</h3>
<p>Архив всех Markdown-файлов одним ZIP.</p>
<span class="landing__cta">Скачать →</span>
</a>
</section>
<p class="landing__version">v{{ buildVersion }}</p>
</div>
<style scoped>
.landing {
min-height: 100vh;
padding: 48px 32px;
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
font-family: var(--vp-font-family-base);
display: flex;
flex-direction: column;
justify-content: center;
gap: 64px;
}
.landing__controls {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
margin-top: 28px;
flex-wrap: wrap;
}
.landing__controls > * {
height: 36px;
box-sizing: border-box;
}
.landing__repo {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0 14px;
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: color 0.15s, border-color 0.15s;
}
.landing__repo:hover {
color: var(--vp-c-text-1);
border-color: var(--vp-c-brand-1);
}
.landing__repo svg {
flex-shrink: 0;
}
.seg {
display: inline-flex;
align-items: stretch;
padding: 4px;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
gap: 2px;
}
.seg__btn {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 6px 14px;
font-size: 13px;
font-weight: 500;
font-family: inherit;
line-height: 1;
color: var(--vp-c-text-2);
background: transparent;
border: none;
border-radius: 999px;
cursor: pointer;
transition: color 0.15s, background-color 0.15s;
}
.seg__btn:hover {
color: var(--vp-c-text-1);
}
.seg__btn--active {
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.seg--icons .seg__btn {
padding: 6px 10px;
}
.seg__btn svg {
display: block;
}
.landing__hero {
text-align: center;
}
.landing__title {
font-size: 56px;
font-weight: 700;
line-height: 1;
margin: 0 0 16px;
letter-spacing: -0.02em;
background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.landing__tagline {
font-size: 18px;
line-height: 1.55;
color: var(--vp-c-text-2);
margin: 0 auto;
max-width: 720px;
}
.landing__cards {
max-width: 1100px;
width: 100%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.landing__card {
display: flex;
flex-direction: column;
gap: 8px;
padding: 24px;
border-radius: 12px;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
text-decoration: none;
color: inherit;
transition: border-color 0.2s, transform 0.2s;
}
.landing__card:hover {
border-color: var(--vp-c-brand-1);
transform: translateY(-2px);
}
.landing__card h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.landing__card p {
margin: 0;
font-size: 14px;
line-height: 1.5;
color: var(--vp-c-text-2);
}
.landing__cta {
margin-top: auto;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
}
.landing__buttons {
margin-top: auto;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.landing__button {
display: inline-flex;
align-items: center;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
font-family: var(--vp-font-family-mono, monospace);
color: var(--vp-c-text-1);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
text-decoration: none;
transition: border-color 0.15s, color 0.15s;
}
.landing__button:hover {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.landing__version {
text-align: center;
margin: 24px 0 0;
font-size: 12px;
color: var(--vp-c-text-3);
font-family: var(--vp-font-family-mono, monospace);
}
@media (max-width: 768px) {
.landing {
padding: 48px 20px 56px;
gap: 40px;
justify-content: flex-start;
}
.landing__title {
font-size: 36px;
}
.landing__tagline {
font-size: 16px;
}
.landing__cards {
grid-template-columns: 1fr;
gap: 16px;
}
.landing__controls {
gap: 8px;
margin-top: 36px;
}
}
@media (max-width: 480px) {
.landing {
padding: 44px 16px 48px;
gap: 36px;
}
.landing__title {
font-size: 30px;
}
.landing__tagline {
font-size: 15px;
line-height: 1.5;
}
.landing__controls {
margin-top: 32px;
}
/* Репозиторий — только иконка, без текста, чтобы все контролы влезли в строку */
.landing__repo {
width: 36px;
padding: 0;
justify-content: center;
}
.landing__repo span {
display: none;
}
.seg__btn {
padding: 6px 12px;
font-size: 12px;
}
.landing__card {
padding: 20px;
}
}
</style>

View File

@@ -1,544 +0,0 @@
import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { execFileSync } from 'node:child_process';
import config from './.vitepress/config';
/** Версия сборки. Передаётся CI через ENV; локально — `dev`. */
const VERSION = process.env.BUILD_VERSION || 'dev';
const BUILD_DATE = new Date().toISOString();
/** Корневая папка для генерируемой статики (попадает в build dist). */
const PUBLIC_DIR = 'docs/public';
/** Префикс URL документации. Соответствует структуре `docs/docs/...`. */
const DOC_PREFIX = '/docs/';
/** Канонический хост сайта (для sitemap/robots). Можно переопределить через ENV. */
const SITE_URL = (process.env.SITE_URL || 'https://nextjs-style-guide.gromlab.ru').replace(/\/$/, '');
interface SidebarItem {
text: string;
link?: string;
items?: SidebarItem[];
collapsed?: boolean;
}
interface Entry {
/** Название группы верхнего уровня (sidebar[].text) */
section: string;
/** Префикс из вложенной группы (например "Архитектура") */
prefix: string | null;
/** Текст пункта в sidebar */
text: string;
/** Ссылка из sidebar */
link: string;
}
/** Разобрать YAML frontmatter (плоский, без вложенностей) */
const parseFrontmatter = (
content: string,
): { data: Record<string, string>; body: string } => {
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
if (!match) return { data: {}, body: content };
const data: Record<string, string> = {};
for (const line of match[1].split('\n')) {
const lineMatch = line.match(/^([^:]+):\s*(.*)$/);
if (!lineMatch) continue;
let value = lineMatch[2].trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
data[lineMatch[1].trim()] = value;
}
return { data, body: match[2] };
};
/** Первый абзац после h1 — однострочное описание для llms.txt */
const firstParagraphAfterH1 = (body: string): string | null => {
const lines = body.split('\n');
const h1Idx = lines.findIndex((l) => /^#\s/.test(l));
if (h1Idx === -1) return null;
let i = h1Idx + 1;
while (i < lines.length && lines[i].trim() === '') i++;
const para: string[] = [];
while (
i < lines.length &&
lines[i].trim() !== '' &&
!lines[i].startsWith('#')
) {
para.push(lines[i].trim());
i++;
}
return para.join(' ').trim() || null;
};
/**
* Преобразовать sidebar `link` (например `/docs/foo`) в относительный
* путь файла внутри `docs/docs/`. Префикс `/docs/` отрезается.
*/
const linkToRel = (link: string): string => {
let rel = link.startsWith(DOC_PREFIX)
? link.slice(DOC_PREFIX.length)
: link.replace(/^\//, '');
if (rel === '' || rel.endsWith('/')) {
rel += 'index.md';
} else {
rel += '.md';
}
return rel;
};
const linkToFilePath = (link: string): string =>
path.join('docs/docs', linkToRel(link));
/** Абсолютный URL `.md`-копии страницы на сайте. */
const linkToSiteUrl = (link: string): string =>
`${DOC_PREFIX}${linkToRel(link)}`;
/**
* Развернуть sidebar в плоский список с сохранением группы и
* накопленного префикса вложенных групп. Поддерживает произвольную
* глубину вложенности — префиксы подгрупп склеиваются через `: `.
*/
const flattenSidebar = (sidebar: SidebarItem[]): Entry[] => {
const entries: Entry[] = [];
const walk = (
items: SidebarItem[],
section: string,
prefix: string | null,
): void => {
for (const item of items) {
const hasChildren = !!item.items && item.items.length > 0;
if (item.link) {
entries.push({ section, prefix, text: item.text, link: item.link });
}
if (hasChildren) {
const nextPrefix = prefix ? `${prefix}: ${item.text}` : item.text;
walk(item.items!, section, nextPrefix);
}
}
};
for (const top of sidebar) {
const hasChildren = !!top.items && top.items.length > 0;
if (top.link && !hasChildren) {
entries.push({ section: top.text, prefix: null, text: top.text, link: top.link });
continue;
}
if (hasChildren) {
walk(top.items!, top.text, null);
}
}
return entries;
};
const groupBySection = (entries: Entry[]): Map<string, Entry[]> => {
const map = new Map<string, Entry[]>();
for (const entry of entries) {
const list = map.get(entry.section);
if (list) list.push(entry);
else map.set(entry.section, [entry]);
}
return map;
};
interface SiteConfig {
title: string;
description: string;
themeConfig: { sidebar: SidebarItem[] };
llmsBlockquote?: string;
llmsContext?: string;
}
const cfg = config as unknown as SiteConfig;
const buildLlms = (): void => {
const sidebar = cfg.themeConfig.sidebar;
const blockquote = cfg.llmsBlockquote ?? cfg.description;
const context = cfg.llmsContext;
const entries = flattenSidebar(sidebar);
const grouped = groupBySection(entries);
const lines: string[] = [];
lines.push(`# ${cfg.title}`);
lines.push('');
lines.push(`> ${blockquote}`);
lines.push('');
if (context) {
lines.push(context);
lines.push('');
}
for (const [section, items] of grouped) {
lines.push(`## ${section}`);
lines.push('');
for (const entry of items) {
const filePath = linkToFilePath(entry.link);
const url = linkToSiteUrl(entry.link);
let description: string | null = null;
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8');
const { data, body } = parseFrontmatter(raw);
description = data.description || firstParagraphAfterH1(body);
} else {
console.warn(`файл не найден: ${filePath}`);
}
const display = entry.prefix
? `${entry.prefix}: ${entry.text}`
: entry.text;
const descPart = description ? `: ${description}` : '';
lines.push(`- [${display}](${url})${descPart}`);
}
lines.push('');
}
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
const outFile = path.join(PUBLIC_DIR, 'llms.txt');
fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
console.log(`${outFile} создан`);
};
/** Рекурсивно скопировать дерево, фильтруя по предикату. */
const copyDirSync = (
src: string,
dest: string,
filter: (name: string) => boolean = () => true,
): number => {
let count = 0;
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
count += copyDirSync(srcPath, destPath, filter);
} else if (entry.isFile() && filter(entry.name)) {
fs.mkdirSync(dest, { recursive: true });
fs.copyFileSync(srcPath, destPath);
count++;
}
}
return count;
};
/**
* Скопировать все `.md`-файлы документации в `docs/public/docs/`,
* чтобы они попали в build `dist/` и были доступны по URL `/docs/path.md`.
*
* `DEVELOP.md` исключается — это точка входа архива, ссылается
* на офлайн-файлы (`MAP.md`), которых нет на сайте.
*/
const copyMdFiles = (): void => {
const srcDir = 'docs/docs';
const destDir = path.join(PUBLIC_DIR, 'docs');
if (!fs.existsSync(srcDir)) return;
fs.rmSync(destDir, { recursive: true, force: true });
const copied = copyDirSync(
srcDir,
destDir,
(name) => name.endsWith('.md') && name !== 'DEVELOP.md',
);
console.log(`скопировано ${copied} .md-файлов в ${destDir}`);
};
/**
* Преобразовать sidebar `link` в относительный путь файла внутри архива
* (от корня папки `nextjs-style-guide/`).
*/
const linkToArchiveRel = (link: string): string => {
let rel = link.startsWith(DOC_PREFIX)
? link.slice(DOC_PREFIX.length)
: link.replace(/^\//, '');
if (rel === '' || rel.endsWith('/')) {
rel += 'index.md';
} else {
rel += '.md';
}
return rel;
};
/**
* Заменить во всех `.md` архива ссылки `[text](/docs/foo)` на относительные
* пути от расположения файла. Без этого внутренние ссылки в распакованной
* папке не работают.
*/
const transformLinksInDir = (rootDir: string): void => {
const linkRe = /\]\(\/docs\/([^)\s#]*)(#[^)]*)?\)/g;
const walk = (dir: string): void => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
continue;
}
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
const content = fs.readFileSync(full, 'utf8');
const fileDir = path.dirname(full);
const updated = content.replace(linkRe, (_match, route, hash = '') => {
const targetRel = linkToArchiveRel(`${DOC_PREFIX}${route}`);
const targetAbs = path.join(rootDir, targetRel);
let rel = path.relative(fileDir, targetAbs);
if (!rel.startsWith('.')) rel = './' + rel;
return `](${rel}${hash})`;
});
if (updated !== content) {
fs.writeFileSync(full, updated, 'utf8');
}
}
};
walk(rootDir);
};
/**
* Собрать `nextjs-style-guide.zip`. Внутри — папка `nextjs-style-guide/`
* с `.md`-файлами, DEVELOP.md (точка входа), MAP.md (навигационная карта)
* и `VERSION`. Внутренние ссылки преобразуются в относительные.
*
* Точка входа архива — `docs/docs/DEVELOP.md`, навигационная карта —
* `docs/docs/MAP.md`. Оба файла редактируются вручную и копируются
* в архив как есть.
*/
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 stage = path.join(tmpRoot, 'nextjs-style-guide');
fs.mkdirSync(stage, { recursive: true });
// 1. Копируем все .md в staging (включая DEVELOP.md и MAP.md).
copyDirSync('docs/docs', stage, (name) => name.endsWith('.md'));
// 2. Удаляем веб-index.md — в архиве его роль выполняет DEVELOP.md.
const indexPath = path.join(stage, 'index.md');
if (fs.existsSync(indexPath)) fs.unlinkSync(indexPath);
// 3. Преобразуем абсолютные ссылки `/docs/...` в относительные.
transformLinksInDir(stage);
// 4. Метаинформация сборки.
fs.writeFileSync(
path.join(stage, 'VERSION'),
`${VERSION}\n${BUILD_DATE}\n`,
);
const outFile = path.resolve(PUBLIC_DIR, 'nextjs-style-guide.zip');
fs.rmSync(outFile, { force: true });
execFileSync('zip', ['-rq', outFile, 'nextjs-style-guide'], {
cwd: tmpRoot,
});
fs.rmSync(tmpRoot, { recursive: true, force: true });
console.log(`${outFile} создан (${VERSION})`);
};
/** Удалить YAML frontmatter из исходника `.md`. */
const stripFrontmatter = (content: string): string =>
content.replace(/^---\n[\s\S]*?\n---\n*/, '');
/**
* Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...).
* Игнорирует строки внутри блоков кода.
*/
const shiftHeadings = (content: string): string => {
const lines = content.split('\n');
let inCodeBlock = false;
return lines
.map((line) => {
if (line.startsWith('```')) inCodeBlock = !inCodeBlock;
if (inCodeBlock) return line;
if (/^#{1,5}\s/.test(line)) return '#' + line;
return line;
})
.join('\n');
};
/**
* Собрать `llms-full.txt` — все страницы в одном файле.
* Порядок страниц повторяет порядок в sidebar.
*/
const buildLlmsFull = (): void => {
const sidebar = cfg.themeConfig.sidebar;
const entries = flattenSidebar(sidebar);
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
const parts: string[] = [];
parts.push(`# ${cfg.title}`);
parts.push('');
if (blockquote) parts.push(`> ${blockquote}`);
if (cfg.llmsContext) {
parts.push('');
parts.push(cfg.llmsContext);
}
parts.push('');
for (const entry of entries) {
const filePath = linkToFilePath(entry.link);
if (!fs.existsSync(filePath)) continue;
const raw = fs.readFileSync(filePath, 'utf8');
const content = shiftHeadings(stripFrontmatter(raw)).trim();
if (!content) continue;
// Мета-якорь: путь страницы для ориентации LLM
parts.push(`<!-- ${entry.link} -->`);
parts.push('');
parts.push(content);
parts.push('');
}
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
const outFile = path.join(PUBLIC_DIR, 'llms-full.txt');
fs.writeFileSync(outFile, parts.join('\n'), 'utf8');
console.log(`${outFile} создан`);
};
/** Манифест сборки — для лендинга и внешних потребителей. */
const writeManifest = (): void => {
const manifest = {
version: VERSION,
buildDate: BUILD_DATE,
llms: '/llms.txt',
llmsFull: '/llms-full.txt',
zip: '/nextjs-style-guide.zip',
};
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(
path.join(PUBLIC_DIR, 'manifest.json'),
JSON.stringify(manifest, null, 2),
'utf8',
);
console.log(`${PUBLIC_DIR}/manifest.json создан`);
};
/**
* Сгенерировать `robots.txt` с указанием sitemap и явными ссылками
* на llms.txt/llms-full.txt — стандартные файлы, которые читают агенты.
*/
const buildRobots = (): void => {
const lines = [
'User-agent: *',
'Allow: /',
'',
`Sitemap: ${SITE_URL}/sitemap.xml`,
'',
'# Карта документации для AI-агентов:',
`# ${SITE_URL}/llms.txt`,
`# ${SITE_URL}/llms-full.txt`,
'',
];
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(path.join(PUBLIC_DIR, 'robots.txt'), lines.join('\n'), 'utf8');
console.log(`${PUBLIC_DIR}/robots.txt создан`);
};
/**
* Сгенерировать `sitemap.xml` из sidebar + корневые ресурсы для LLM
* (llms.txt, llms-full.txt) — чтобы агенты, читающие sitemap, видели их.
*/
const buildSitemap = (): void => {
const sidebar = cfg.themeConfig.sidebar;
const entries = flattenSidebar(sidebar);
const urls = new Set<string>();
urls.add(`${SITE_URL}/`);
urls.add(`${SITE_URL}/llms.txt`);
urls.add(`${SITE_URL}/llms-full.txt`);
for (const entry of entries) {
const link = entry.link;
// cleanUrls: канон без `.html`. Index-страницы — каталог со слешем.
urls.add(`${SITE_URL}${link}`);
}
const today = BUILD_DATE.slice(0, 10);
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...[...urls].map(
(loc) => ` <url><loc>${loc}</loc><lastmod>${today}</lastmod></url>`,
),
'</urlset>',
'',
].join('\n');
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync(path.join(PUBLIC_DIR, 'sitemap.xml'), xml, 'utf8');
console.log(`${PUBLIC_DIR}/sitemap.xml создан`);
};
/**
* Преобразовать абсолютные ссылки `index.md` в рабочие при открытии
* README в репозитории:
* - `/docs/foo` → относительный путь `docs/docs/foo.md`;
* - корневые ресурсы (`/llms.txt`, `/llms-full.txt`, `*.zip`, `/manifest.json`,
* `/sitemap.xml`, `/robots.txt`) — генерируемые, в репозитории отсутствуют,
* поэтому ссылки переписываются на абсолютный `SITE_URL`.
*/
const transformReadmeLinks = (content: string): string => {
const linkRe = /\]\((\/[^)\s]*)\)/g;
return content.replace(linkRe, (match, href: string) => {
const [pathPart, hash = ''] = href.split('#');
const hashPart = hash ? `#${hash}` : '';
if (pathPart.startsWith(DOC_PREFIX)) {
const rel = linkToArchiveRel(pathPart);
return `](docs/docs/${rel}${hashPart})`;
}
return `](${SITE_URL}${pathPart}${hashPart})`;
});
};
/** Скопировать `index.md` документации в корневой README без frontmatter. */
const buildReadme = (): void => {
const indexPath = 'docs/docs/index.md';
if (!fs.existsSync(indexPath)) {
console.warn(`Пропуск README.md: ${indexPath} не найден`);
return;
}
const raw = fs.readFileSync(indexPath, 'utf8');
const { body } = parseFrontmatter(raw);
const transformed = transformReadmeLinks(body.trimStart());
// Порядок: H1 → описание (первый абзац) → ссылка на сайт.
const withSiteLink = transformed.replace(
/^(#\s[^\n]*\n\n[^\n]+\n)/,
`$1\nСайт: ${SITE_URL}\n`,
);
fs.writeFileSync('README.md', withSiteLink, 'utf8');
console.log(`README.md обновлён из ${indexPath}`);
};
buildLlms();
buildLlmsFull();
copyMdFiles();
buildZip();
writeManifest();
buildRobots();
buildSitemap();
buildReadme();

23
notes Normal file
View File

@@ -0,0 +1,23 @@
# TODO
## Триггеры: классификация и расширение
Текущий список триггеров слабо проработан. Нужно:
1. Классифицировать триггеры по группам:
- Создание — новые модули, компоненты, страницы
- Ресурсы — ассеты (иконки, шрифты, изображения, видео)
- Данные — API, сторы, серверные данные, формы
- Навигация — роутинг, middleware, редиректы
- Модификация — рефакторинг, перенос, удаление
- Инфраструктура — зависимости, переводы, настройка окружения
2. Добавить недостающие триггеры. Примеры пробелов:
- Создание: утилита/хелпер, тип/интерфейс, контекст
- Данные: создать форму, добавить валидацию
- Навигация: динамический роут, middleware, редирект
- Модификация: рефакторинг компонента, перенос модуля, удаление модуля
- Обработка ошибок: error boundary, fallback UI, error.tsx, not-found.tsx, loading.tsx
- Авторизация: защита страницы, проверка прав
3. Обновить секцию "Триггеры" в DEVELOP.md — перейти на новые группы.

532
package-lock.json generated
View File

@@ -1,14 +1,13 @@
{ {
"name": "nextjs-style-guide", "name": "frontend-style-guide",
"version": "0.0.0", "version": "0.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nextjs-style-guide", "name": "frontend-style-guide",
"version": "0.0.0", "version": "0.0.0",
"devDependencies": { "devDependencies": {
"tsx": "^4.19.2",
"vitepress": "^1.6.3" "vitepress": "^1.6.3"
} }
}, },
@@ -644,23 +643,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
"integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
@@ -678,23 +660,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
"integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
@@ -712,23 +677,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
"integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
@@ -1711,19 +1659,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/hast-util-to-html": { "node_modules/hast-util-to-html": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@@ -2153,16 +2088,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/rfdc": { "node_modules/rfdc": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@@ -2323,459 +2248,6 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
"integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
"integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
"integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/android-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
"integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
"integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
"integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
"integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
"integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
"integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
"integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
"integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
"integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
"integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
"integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
"integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
"integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
"integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
"integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
"integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
"integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
"integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
"integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
"integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/tsx/node_modules/esbuild": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
"integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.7",
"@esbuild/android-arm": "0.27.7",
"@esbuild/android-arm64": "0.27.7",
"@esbuild/android-x64": "0.27.7",
"@esbuild/darwin-arm64": "0.27.7",
"@esbuild/darwin-x64": "0.27.7",
"@esbuild/freebsd-arm64": "0.27.7",
"@esbuild/freebsd-x64": "0.27.7",
"@esbuild/linux-arm": "0.27.7",
"@esbuild/linux-arm64": "0.27.7",
"@esbuild/linux-ia32": "0.27.7",
"@esbuild/linux-loong64": "0.27.7",
"@esbuild/linux-mips64el": "0.27.7",
"@esbuild/linux-ppc64": "0.27.7",
"@esbuild/linux-riscv64": "0.27.7",
"@esbuild/linux-s390x": "0.27.7",
"@esbuild/linux-x64": "0.27.7",
"@esbuild/netbsd-arm64": "0.27.7",
"@esbuild/netbsd-x64": "0.27.7",
"@esbuild/openbsd-arm64": "0.27.7",
"@esbuild/openbsd-x64": "0.27.7",
"@esbuild/openharmony-arm64": "0.27.7",
"@esbuild/sunos-x64": "0.27.7",
"@esbuild/win32-arm64": "0.27.7",
"@esbuild/win32-ia32": "0.27.7",
"@esbuild/win32-x64": "0.27.7"
}
},
"node_modules/unist-util-position": { "node_modules/unist-util-position": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",

View File

@@ -1,17 +1,16 @@
{ {
"name": "nextjs-style-guide", "name": "frontend-style-guide",
"private": true, "private": true,
"type": "module", "type": "module",
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"llms": "tsx ./generate-llms.ts", "build:ai": "node ./scripts/build-ai.js",
"dev": "vitepress dev .", "dev": "vitepress dev .",
"build": "vitepress build .", "build": "vitepress build .",
"serve": "vitepress serve ." "serve": "vitepress serve ."
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"tsx": "^4.19.2",
"vitepress": "^1.6.3" "vitepress": "^1.6.3"
} }
} }

88
scripts/build-ai.js Normal file
View File

@@ -0,0 +1,88 @@
import fs from "fs";
import path from "path";
import { pathToFileURL } from "url";
const SRC_DIR = "./src";
const DIST_DIR = "./dist/ai";
const SCRIPTS_DIR = "./scripts";
// ---------------------------------------------------------------------------
// Сборка по манифесту
// ---------------------------------------------------------------------------
async function buildForFramework(framework) {
const manifestPath = path.join(SCRIPTS_DIR, `${framework}.build.js`);
if (!fs.existsSync(manifestPath)) {
console.error(`Манифест не найден: ${manifestPath}`);
process.exit(1);
}
const manifest = (await import(pathToFileURL(path.resolve(manifestPath)).href)).default;
const outDir = path.join(DIST_DIR, framework);
// Очищаем выходную директорию
if (fs.existsSync(outDir)) {
fs.rmSync(outDir, { recursive: true, force: true });
}
console.log(`\nСборка: ${manifest.name} (${framework})`);
console.log(`Выход: ${outDir}\n`);
const errors = [];
let count = 0;
for (const [destRelative, srcRelative] of Object.entries(manifest.files)) {
const srcPath = path.join(SRC_DIR, srcRelative);
const destPath = path.join(outDir, destRelative);
if (!fs.existsSync(srcPath)) {
errors.push(` [!] Не найден: ${srcRelative}`);
continue;
}
fs.mkdirSync(path.dirname(destPath), { recursive: true });
fs.copyFileSync(srcPath, destPath);
console.log(` ${destRelative}`);
count++;
}
if (errors.length > 0) {
console.log(`\nОшибки:`);
errors.forEach((e) => console.log(e));
}
console.log(`\nГотово: ${outDir} (${count} файлов)`);
}
// ---------------------------------------------------------------------------
// Определяем что собирать
// ---------------------------------------------------------------------------
let frameworks = fs
.readdirSync(SCRIPTS_DIR)
.filter((f) => f.endsWith(".build.js"))
.map((f) => f.replace(".build.js", ""));
if (frameworks.length === 0) {
console.error("Не найдено ни одного манифеста *.build.js в scripts/");
process.exit(1);
}
// --framework=nextjs
const fwArg = process.argv.find((a) => a.startsWith("--framework="));
if (fwArg) {
const fw = fwArg.split("=")[1];
if (frameworks.includes(fw)) {
frameworks = [fw];
} else {
console.error(`Фреймворк "${fw}" не найден. Доступные: ${frameworks.join(", ")}`);
process.exit(1);
}
}
for (const fw of frameworks) {
await buildForFramework(fw);
}
console.log("\nВсе сборки завершены.");

69
scripts/nextjs.build.js Normal file
View File

@@ -0,0 +1,69 @@
/**
* Манифест сборки стайлгайда для Next.js.
*
* Ключ — путь файла в dist/ai/nextjs/.
* Значение — путь исходника относительно src/.
*
* Скрипт только копирует. Никакой генерации.
*/
export default {
name: "Next.js",
files: {
// ── Точки входа ─────────────────────────────────────────────
"DEVELOP.md": "nextjs/DEVELOP.md",
// ── Базовые правила ─────────────────────────────────────────
"basics/architecture.md": "base/basics/architecture.md",
"basics/code-style.md": "base/basics/code-style.md",
"basics/documentation.md": "base/basics/documentation.md",
"basics/naming.md": "base/basics/naming.md",
"basics/tech-stack.md": "base/basics/tech-stack.md",
"basics/typing.md": "base/basics/typing.md",
// ── Прикладные разделы ──────────────────────────────────────
"applied/components.md": "base/applied/components.md",
"applied/styles.md": "base/applied/styles.md",
"applied/templates-generation.md": "base/applied/templates-generation.md",
"applied/hooks.md": "base/applied/hooks.md",
"applied/stores.md": "base/applied/stores.md",
"applied/api.md": "base/applied/api.md",
"applied/fonts.md": "base/applied/fonts.md",
"applied/localization.md": "base/applied/localization.md",
"applied/images-sprites.md": "base/applied/images-sprites.md",
"applied/svg-sprites.md": "base/applied/svg-sprites.md",
"applied/video.md": "base/applied/video.md",
"applied/vscode.md": "base/applied/vscode.md",
"applied/page-level.md": "nextjs/applied/page-level.md",
"applied/project-structure.md": "nextjs/applied/project-structure.md",
// ── Триггеры: разработка / создание ─────────────────────────
"triggers/develop/create-component.md": "base/triggers/develop/create-component.md",
"triggers/develop/create-feature.md": "base/triggers/develop/create-feature.md",
"triggers/develop/create-widget.md": "base/triggers/develop/create-widget.md",
"triggers/develop/create-entity.md": "base/triggers/develop/create-entity.md",
"triggers/develop/create-hook.md": "base/triggers/develop/create-hook.md",
"triggers/develop/create-store.md": "base/triggers/develop/create-store.md",
"triggers/develop/create-page.md": "nextjs/triggers/develop/create-page.md",
"triggers/develop/create-layout.md": "nextjs/triggers/develop/create-layout.md",
"triggers/develop/create-project.md": "nextjs/triggers/develop/create-project.md",
"triggers/develop/generate-module.md": "base/triggers/develop/generate-module.md",
// ── Триггеры: разработка / стилизация и ресурсы ─────────────
"triggers/develop/style-component.md": "base/triggers/develop/style-component.md",
"triggers/develop/add-icon.md": "base/triggers/develop/add-icon.md",
"triggers/develop/add-image.md": "base/triggers/develop/add-image.md",
"triggers/develop/add-video.md": "base/triggers/develop/add-video.md",
"triggers/develop/add-font.md": "base/triggers/develop/add-font.md",
// ── Триггеры: разработка / данные и состояние ───────────────
"triggers/develop/add-api-request.md": "base/triggers/develop/add-api-request.md",
"triggers/develop/connect-store.md": "base/triggers/develop/connect-store.md",
"triggers/develop/add-server-data.md": "nextjs/triggers/develop/add-server-data.md",
// ── Триггеры: разработка / инфраструктура ───────────────────
"triggers/develop/add-localization.md": "base/triggers/develop/add-localization.md",
"triggers/develop/add-dependency.md": "base/triggers/develop/add-dependency.md",
"triggers/develop/setup-vscode.md": "base/triggers/develop/setup-vscode.md",
},
};

5
src/base/applied/api.md Normal file
View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [api, запрос, fetch, SWR, эндпоинт, REST, клиент]
when: "Работа с API: запросы, клиенты, обработка ответов"
---

View File

@@ -0,0 +1,118 @@
---
title: Компоненты
scope: applied
keywords: [компонент, props, jsx, ui, clsx, cl, React, FC]
when: "Создание или редактирование React-компонентов: структура, пропсы, стили"
---
# Компоненты
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
Архитектурные слои и их назначение описаны в разделе [Архитектура](/basics/architecture).
## Правила организации
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. Осознанное исключение из [базового правила](/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'
```
## Дочерние компоненты
Если модулю нужны внутренние подкомпоненты — генерировать их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.

View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [шрифт, font, next/font, подключение шрифта, woff]
when: "Подключение и настройка шрифтов"
---

View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [хук, hook, use, кастомный хук, useState, useEffect]
when: "Создание или использование кастомных хуков"
---

View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [изображение, картинка, image, next/image, public, оптимизация]
when: "Работа с изображениями: подключение, оптимизация"
---

View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [i18n, локализация, перевод, язык, i18next, namespace]
when: "Локализация: добавление переводов, работа с i18next"
---

View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [стор, store, zustand, состояние, глобальное состояние]
when: "Работа с глобальным состоянием: создание стора, подписка"
---

View File

@@ -1,18 +1,18 @@
--- ---
title: Использование стилей title: Стили
description: Как пишутся стили в проекте. scope: applied
keywords: [css, postcss, модули, css modules, токены, медиа-запросы, вложенность, класс]
when: "Стилизация: CSS Modules, PostCSS, переменные, медиа-запросы"
--- ---
# Стили
# Использование стилей Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование.
Как пишутся стили в проекте.
## Общие правила ## Общие правила
- Только **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`.
**Хорошо** **Хорошо**
@@ -144,13 +144,13 @@ description: Как пишутся стили в проекте.
## CSS-переменные ## CSS-переменные
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `src/shared/styles/variables.css` через `:root`. - Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `app/styles/variables.css` через `:root`.
- Файл переменных подключается через `src/shared/styles/global.css`, который импортируется один раз в `src/app/layout.tsx`. - Файл переменных подключается один раз в корневом layout/entry point — после этого переменные доступны глобально через каскад.
- Не дублировать магические значения в компонентах. - Не дублировать магические значения в компонентах.
**Хорошо** **Хорошо**
```css ```css
/* src/shared/styles/variables.css */ /* app/styles/variables.css */
:root { :root {
--color-primary: #3b82f6; --color-primary: #3b82f6;
--color-bg: #ffffff; --color-bg: #ffffff;
@@ -184,11 +184,11 @@ description: Как пишутся стили в проекте.
## Custom Media ## Custom Media
- Breakpoints определяются через Custom Media Queries в `src/shared/styles/media.css`. - Breakpoints определяются через Custom Media Queries в `app/styles/media.css`.
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей. - Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
```css ```css
/* src/shared/styles/media.css */ /* app/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);
@@ -269,3 +269,17 @@ description: Как пишутся стили в проекте.
- Желательно не писать комментарии в CSS. - Желательно не писать комментарии в CSS.
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение. - Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.
## Приоритет стилизации
Основной UI-фреймворк проекта — **Mantine**. При стилизации компонентов придерживаться следующего приоритета:
1. **Mantine-компоненты и их пропсы** — в первую очередь использовать встроенные возможности Mantine (пропсы, `classNames`, `styles`).
2. **Глобальные CSS-токены** (`--color-*`, `--space-*`, `--radius-*`) — для значений, которые не покрываются Mantine.
3. **PostCSS Modules** — когда Mantine не покрывает задачу и нужна кастомная стилизация.
## Что запрещено
- **Инлайн-стили** — использование атрибута `style` в компонентах строго запрещено.
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
- **Глобальные стили** вне `app/styles/` запрещены.

View File

@@ -0,0 +1,7 @@
---
title: SVG-спрайты
scope: applied
keywords: [svg, спрайт, иконка, icon, sprite]
when: "Работа с SVG-иконками и спрайтами"
---
# SVG-спрайты

View File

@@ -0,0 +1,175 @@
---
title: Шаблоны и генерация кода
scope: applied
keywords: [шаблон, генерация, template, scaffold, plop, hygen, .templates]
when: "Генерация кода из шаблонов, создание новых шаблонов"
---
<!-- @formatter:off -->
::: v-pre
# Шаблоны и генерация кода
Как работают шаблоны, как их создавать, синтаксис переменных и как генерировать код с помощью расширения VS Code и CLI.
## Структура шаблонов
Все шаблоны лежат в `.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 feature auth src/features` | Фичу |
| `npx @gromlab/create widget header src/widgets` | Виджет |
| `npx @gromlab/create entity user src/entities` | Сущность |
| `npx @gromlab/create layout admin src/layouts` | Layout |
| `npx @gromlab/create store auth src/shared/model` | Стор |
:::
## Какие модули генерируются из шаблонов
| Модуль | Слой | Шаблон |
|---|---|---|
| Компонент | `shared/ui/` | `component` |
| Фича | `features/` | `feature` |
| Виджет | `widgets/` | `widget` |
| Сущность | `entities/` | `entity` |
| Layout | `layouts/` | `layout` |
| Экран | `screens/` | `screen` |
| Стор | `model/` | `store` |
## Когда создавать новый шаблон
- Повторяющаяся структура появляется больше одного раза.
- Существующий шаблон не покрывает нужный тип модуля.

View File

@@ -0,0 +1,5 @@
---
scope: applied
keywords: [видео, video, плеер, mp4]
when: "Встраивание и работа с видео"
---

View File

@@ -1,11 +1,12 @@
--- ---
title: VS Code title: Настройка VS Code
description: Единые настройки редактора и расширений для команды. scope: applied
keywords: [vscode, редактор, расширение, настройка, extension, .vscode]
when: "Настройка VS Code: расширения, settings.json, сниппеты"
--- ---
# Настройка VS Code
# VS Code Каждый проект содержит папку `.vscode/` с конфигурацией редактора. Это гарантирует, что все участники команды работают с одинаковыми настройками форматирования, линтинга и расширениями.
Единые настройки редактора и расширений для команды.
## Структура `.vscode/` ## Структура `.vscode/`

View File

@@ -0,0 +1,665 @@
---
title: Архитектура
scope: basics
keywords: [SLM Design, слой, модуль, сегмент, архитектура, FSD, scoped layered module]
when: "Организация кода: слои, модули, зависимости между модулями"
---
<!-- /index -->
# SLM Design
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
## Преимущества
### Вертикальная организация домена
Бизнес-домен не разбивается по техническим слоям — сценарии, сущности, типы и UI живут в одном модуле. Это сокращает время навигации и упрощает сопровождение: все изменения домена локализованы.
### Dependency Injection без фреймворков
Cross-domain зависимости в бизнес-слое реализуются через фабрики — модуль декларирует что ему нужно, а точка использования предоставляет зависимости. Домены изолированы без DI-контейнеров, провайдеров и шин событий.
### Разделение ответственности без перегрузки слоёв
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
### Горизонтальная инкапсуляция
Вложенные модули (`parts/`) и направление зависимостей позволяют нескольким разработчикам работать над одной областью приложения параллельно, не затрагивая код друг друга.
### Колокация по умолчанию
Код начинает жизнь рядом с местом использования и поднимается в общие слои только при реальной потребности. Глобальные слои не засоряются преждевременными абстракциями.
### Явное разделение каркаса и контента
Каркас группы маршрутов (`layouts/`) и контент конкретной страницы (`screens/`) — независимые слои с собственной ответственностью.
### Масштабирование через группировку
При росте проекта слои не теряют структуру — модули группируются по естественным признакам: бизнес-домены по субдоменам, страницы по разделам, UI-компоненты по уровню абстракции (примитивы и композиции).
## Происхождение
SLM Design вырос на основе:
- **Feature-Sliced Design** — слоистая структура, публичный API модуля, направление зависимостей
- **Vertical Slice Architecture** — модуль как вертикальный срез, содержащий всё необходимое
- **Screaming Architecture** — структура проекта «кричит» о назначении: открыл `business/auth` — видишь авторизацию
- **Colocation Principle** — код живёт рядом с местом использования
## Пример структуры проекта
```text
src/
├── app/
├── layouts/
│ ├── main/
│ └── dashboard/
├── screens/
│ ├── home/
│ ├── products/
│ ├── product-detail/
│ └── about/
├── widgets/
│ ├── page-heading/
│ ├── hero-section/
│ └── promo-banner/
├── business/
│ ├── auth/
│ ├── catalog/
│ ├── orders/
│ └── chat/
├── infrastructure/
│ ├── theme/
│ ├── i18n/
│ ├── backend-api/
│ └── logger/
├── ui/
│ ├── button/
│ ├── input/
│ ├── modal/
│ ├── toast/
│ └── dropdown/
└── shared/
├── lib/
├── types/
└── styles/
```
## Принципы
- **Домен — единое целое.** Всё, что относится к домену, живёт в одном модуле.
- **Колокация.** Код рождается рядом с местом использования и поднимается только при необходимости.
- **Зависимости однонаправлены.** Импорты только сверху вниз, только через публичный API.
- **Архитектура — каркас, не клетка.** Правила фиксируют направление зависимостей и структуру модуля, остальное определяет команда.
<!-- /reference/layers -->
## Слои
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
### Определение
**Слой — уровень организации кода внутри `src/`. Каждый слой отвечает за свою область (каркас страницы, бизнес-логика, UI-кит) и задаёт правила для кода внутри: направление импортов, именование, допустимые связи между модулями.**
### Группы слоёв
Слои делятся на три группы:
| Группа | Слои | Описание |
|--------|------|----------|
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
### Направление зависимостей
Любой импорт между модулями — только через публичный API.
```
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
```
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
- Модули одного слоя в группе «Композиция» изолированы друг от друга
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
### Слой App
Точка входа приложения. Отвечает за запуск, роутинг и композицию маршрутов из layout и screen.
В отличие от остальных слоёв, `app/` не содержит модулей SLM. Здесь живут только инфраструктурные файлы, которые не могут быть никаким другим слоем: файлы фреймворка роутинга, точка запуска и код инициализации.
#### Требования
- Не содержит модулей SLM — только файлы фреймворка, роутинг, инициализация
- Содержит: файлы маршрутов, bootstrap, обработку ошибок верхнего уровня (404, error boundary), подключение глобальных стилей и ассетов
- Провайдеры и гарды — только подключает готовые из нижних слоёв, не реализует
- Не содержит бизнес-логику, UI-компоненты, хуки, сторы, сервисы
- Никем не импортируется
### Слой Layouts
Каркас страницы: общие элементы, одинаковые для группы маршрутов (header, footer, sidebar).
```text
src/layouts/
├── main/
├── dashboard/
└── auth/
```
#### Требования
- Содержит только модули
- Не содержит бизнес-логику
- Контекстно-зависимые блоки принимает через пропсы от `app`, не импортирует напрямую
### Слой Screens
Контент конкретной страницы: собирает её из модулей нижних слоёв.
```text
src/screens/
├── home/
├── products/
├── product-detail/
├── about/
└── contacts/
```
Когда количество страниц затрудняет навигацию — вводится группировка по разделам. Группа — папка для организации, не модуль (без `index.ts`).
```text
src/screens/
├── shop/
│ ├── home/
│ ├── products/
│ ├── product-detail/
│ └── cart/
├── account/
│ ├── profile/
│ ├── settings/
│ └── order-history/
└── info/
├── about/
├── contacts/
└── faq/
```
#### Требования
- Содержит только модули
- Не содержит бизнес-логику
- Локальные одноразовые секции живут внутри screen-модуля, не выносятся в `widgets`/`business`
### Слой Widgets
Составной блок интерфейса, который компонует модули ядра, но не принадлежит конкретному бизнес-домену. Widget появляется когда блок используется в нескольких screens или layouts.
Если блок принадлежит домену — он живёт в `business/{area}/`, даже если переиспользуется. Если блок нужен только в одном месте — это `screens/{name}/parts/` или `layouts/{name}/parts/`, а не widget.
```text
src/widgets/
├── page-heading/
├── hero-section/
├── onboarding-checklist/
├── promo-banner/
└── error-boundary/
```
#### Требования
- Не принадлежит конкретному бизнес-домену. Если блок доменный — он живёт в `business/`
- Используется в нескольких screens или layouts
### Слой Business
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую.
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
```text
src/business/
├── auth/
├── catalog/
├── orders/
├── checkout/
└── chat/
```
Когда количество доменов затрудняет навигацию — вводится группировка по субдоменам. Группа — папка для организации, не модуль (без `index.ts`).
```text
src/business/
├── commerce/
│ ├── catalog/
│ ├── cart/
│ ├── orders/
│ └── checkout/
└── communication/
├── chat/
└── notifications/
```
#### Требования
- Один модуль = один бизнес-домен
- Циклические зависимости между доменами запрещены
- Импорт кода между доменами — через фабрику. `import type` — напрямую
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
### Слой Infrastructure
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`.
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
```text
src/infrastructure/
├── theme/
├── i18n/
├── backend-api/
├── maps-api/
├── logger/
├── feature-flags/
└── realtime/
```
#### Требования
- Один модуль = один техсервис
- Импортирует `infrastructure/`, `ui/`, `shared/`
### Слой UI
UI-кит без бизнес-логики: button, carousel, toast, modal.
Слой входит в группу «Ядро». Импортирует `ui/` и `shared/`.
Компоненты строятся друг на друге: `button` использует `icon`, `carousel` использует `button`.
```text
src/ui/
├── button/
├── input/
├── icon/
├── carousel/
├── modal/
├── toast/
├── dropdown/
├── tabs/
└── tooltip/
```
Когда количество компонентов затрудняет навигацию — вводится группировка на примитивы и композиции. Примитивы (`button`, `icon`, `input`) не импортируют композиции. Композиции (`carousel`, `modal`, `dropdown`) строятся на примитивах.
```text
src/ui/
├── primitives/
│ ├── button/
│ ├── input/
│ ├── icon/
│ └── badge/
└── composites/
├── carousel/
├── modal/
├── dropdown/
├── tabs/
└── tooltip/
```
#### Требования
- Не содержит бизнес-логику
- Импортирует только `ui/` и `shared/`
### Слой Shared
Общие ресурсы: утилиты, хелперы, стили, конфиги. Не знает о бизнес-домене.
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
```text
src/shared/
├── lib/
├── types/
├── styles/
└── sprites/
```
#### Требования
- Не имеет runtime-состояния
<!-- /reference/modules -->
## Модули
Раздел описывает модули 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
```
Подробное описание каждого сегмента — в разделе [Сегменты](/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/`
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
<!-- /reference/segments -->
## Сегменты
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
### Определение
**Сегмент — папка внутри модуля, которая группирует файлы по назначению. Набор сегментов не фиксирован — модуль включает только те, которые ему нужны. Команда сама определяет какие сегменты используются в проекте — архитектура даёт рекомендацию.**
### Обзор
| Сегмент | Содержимое |
|---------|------------|
| `ui/` | Компоненты модуля — только `.tsx` файлы |
| `parts/` | Вложенные модули со своими сегментами |
| `hooks/` | React-хуки |
| `stores/` | Сторы состояния |
| `services/` | Работа с внешними источниками данных |
| `mappers/` | Трансформация данных между форматами |
| `types/` | TypeScript-типы и интерфейсы |
| `styles/` | Стили |
| `lib/` | Утилиты и хелперы модуля |
| `config/` | Константы и конфигурация |
### Сегмент ui/
Компоненты, принадлежащие модулю. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков. Использует сегменты родительского модуля.
```text
auth/
├── ui/
│ ├── auth-provider.tsx
│ ├── auth-guard.tsx
│ └── logout-button.tsx
├── types/
├── hooks/
└── index.ts
```
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`.
### Сегмент parts/
Вложенные модули со своими сегментами. Каждый элемент `parts/` — полноценный модуль: папка с компонентом, хуками, стилями, типами и т.д.
```text
home/
├── parts/
│ ├── hero-section/
│ │ ├── hero-section.tsx
│ │ ├── styles/
│ │ └── parts/
│ │ └── top-banner/
│ │ └── top-banner.tsx
│ └── features-section/
│ ├── features-section.tsx
│ └── hooks/
├── home.screen.tsx
└── index.ts
```
Отличие от `ui/`: элемент `parts/` — модуль со своими сегментами. Элемент `ui/` — компонент, один `.tsx` файл.
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
Если вложенный модуль обрастает своими `parts/` — это сигнал, что он достаточно самостоятельный для подъёма на уровень выше.
### Сегмент hooks/
React-хуки модуля. Инкапсулируют логику, состояние, подписки, побочные эффекты.
```text
hooks/
├── use-auth.hook.ts
├── use-session.hook.ts
└── use-permissions.hook.ts
```
### Сегмент stores/
Сторы состояния модуля. Конкретная реализация зависит от выбранного стейт-менеджера (Zustand, MobX, Redux и т.д.).
```text
stores/
├── auth.store.ts
└── session.store.ts
```
### Сегмент services/
Работа с внешними источниками данных: API-вызовы, запросы, подписки.
```text
services/
├── auth.service.ts
└── token.service.ts
```
### Сегмент mappers/
Функции трансформации данных из одного формата в другой: DTO в доменный тип, доменный тип в DTO, доменный тип в ViewModel.
```text
mappers/
├── map-user.ts
├── map-product.ts
└── map-order-to-dto.ts
```
### Сегмент types/
TypeScript-типы и интерфейсы модуля. Доменные типы, DTO, пропсы компонентов.
```text
types/
├── user.type.ts
└── session.type.ts
```
### Сегмент styles/
Стили модуля. Формат зависит от выбранного подхода (CSS Modules, SCSS, CSS-in-JS и т.д.).
```text
styles/
├── auth.module.css
└── login-form.module.css
```
### Сегмент lib/
Утилиты и хелперы, специфичные для модуля. Чистые функции без побочных эффектов.
```text
lib/
├── validate-email.ts
└── format-phone.ts
```
Отличие от `shared/lib/`: здесь лежат утилиты, нужные только этому модулю. Общие утилиты — в `shared/lib/`.
### Сегмент config/
Константы и конфигурация модуля: маршруты, лимиты, дефолтные значения.
```text
config/
├── routes.ts
└── constants.ts
```

View File

@@ -1,11 +1,12 @@
--- ---
title: Стиль кода title: Стиль кода
description: Как оформляется код в проекте. scope: basics
keywords: [форматирование, импорт, отступ, кавычки, early return, точка с запятой, линтер]
when: "Написание или ревью любого кода: форматирование, импорты, структура файла"
--- ---
# Стиль кода # Стиль кода
Как оформляется код в проекте. Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
## Отступы ## Отступы

View File

@@ -1,11 +1,13 @@
--- ---
title: Документирование title: Документирование
description: Что и как документировать в коде. scope: basics
keywords: [JSDoc, комментарий, документирование, описание функции, описание компонента]
when: "Документирование кода: JSDoc для функций, компонентов, типов"
--- ---
# Документирование # Документирование
Что и как документировать в коде. Этот раздел описывает правила документирования кода: когда и как писать
комментарии к компонентам, функциям, типам и интерфейсам.
## Общие правила ## Общие правила

View File

@@ -0,0 +1,24 @@
---
title: Начало работы
scope: workflow
keywords: [начало, onboarding, настройка, установка, первый запуск]
when: "Первый запуск проекта, знакомство со стеком"
---
# Начало работы
Что нужно знать перед началом разработки в проекте.
## Стек проекта
**Next.js** (App Router), **Mantine**, **Zustand**, **FSD**.
Подробнее — [Технологии и библиотеки](/basics/tech-stack).
## Ключевые особенности
- **Генерация вместо ручного создания** — компоненты, фичи, виджеты, сторы и другие модули не создаются вручную. Файловая структура генерируется из шаблонов `.templates/`. Ручное создание файловой структуры модулей запрещено.
- **Biome вместо ESLint + Prettier** — один инструмент для линтинга и форматирования. Автофикс и сортировка импортов происходят автоматически при сохранении файла.
## Настройка окружения
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/applied/vscode).

View File

@@ -1,11 +1,12 @@
--- ---
title: Именование title: Именование
description: Как называть переменные, файлы и прочие сущности в коде. scope: basics
keywords: [camelCase, kebab-case, PascalCase, имя файла, имя переменной, имя компонента, имя хука]
when: "Создание файлов, переменных, компонентов, хуков — выбор имени"
--- ---
# Именование # Именование
Как называть переменные, файлы и прочие сущности в коде. Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту.
## Базовые правила ## Базовые правила
@@ -34,12 +35,6 @@ 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` — интерфейсы
@@ -60,7 +55,7 @@ description: Как называть переменные, файлы и про
**Хорошо** **Хорошо**
```text ```text
business/ features/
└── auth-by-email/ └── auth-by-email/
├── ui/ ├── ui/
│ └── login-form.tsx │ └── login-form.tsx
@@ -69,14 +64,14 @@ business/
├── stores/ ├── stores/
│ └── auth.store.ts │ └── auth.store.ts
├── types/ ├── types/
│ └── auth.type.ts │ └── auth.interface.ts
├── auth-by-email.tsx ├── auth-by-email.feature.tsx
└── index.ts └── index.ts
``` ```
**Плохо** **Плохо**
```text ```text
business/ features/
└── authByEmail/ └── authByEmail/
├── LoginForm.tsx ├── LoginForm.tsx
├── useAuth.ts ├── useAuth.ts

View File

@@ -1,11 +1,12 @@
--- ---
title: Технологии и библиотеки title: Технологии и библиотеки
description: Какие библиотеки и инструменты используются в проекте. scope: basics
keywords: [стек, React, TypeScript, Next.js, Mantine, библиотека, зависимость]
when: "Выбор библиотеки или технологии, проверка допустимости зависимости"
--- ---
# Технологии и библиотеки # Технологии и библиотеки
Какие библиотеки и инструменты используются в проекте. Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте.
## Что используем ## Что используем
@@ -14,7 +15,7 @@ description: Какие библиотеки и инструменты испо
- `Next.js` — для продуктовых сайтов. - `Next.js` — для продуктовых сайтов.
### Архитектура ### Архитектура
- `SLM Design`собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/docs/basics/architecture/). - `FSD (Feature-Sliced Design)`структура проекта и границы модулей. Используется кастомизированная версия — подробнее в разделе [Архитектура](/basics/architecture).
### UI компоненты ### UI компоненты
- `Mantine UI` — базовые UI-компоненты. - `Mantine UI` — базовые UI-компоненты.

View File

@@ -1,24 +1,20 @@
--- ---
title: Типизация title: Типизация
description: Как типизируется код в проекте. scope: basics
keywords: [type, interface, generic, any, unknown, enum, типизация, пропсы]
when: "Типизация кода: выбор type vs interface, работа с generic, запрет any"
--- ---
# Типизация # Типизация
Как типизируется код в проекте. Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`.
## Общие правила ## Общие правила
- Указывать типы для параметров компонентов и параметров функций. - Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов. - Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
- Избегать `any` и `unknown` без необходимости. - Избегать `any` и `unknown` без необходимости.
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины. - Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
## React-компоненты
- Пропсы компонента типизировать через отдельный `Props`.
- Возвращаемый тип компонента не указывать: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
## Функции ## Функции
- Для публичных функций указывать возвращаемый тип. - Для публичных функций указывать возвращаемый тип.

View File

@@ -0,0 +1,35 @@
---
title: Добавить API-запрос
---
# Добавить API-запрос
Инструкция по добавлению запроса к серверу: создание клиента, хука, обработка ответа.
## Прочитай перед началом
- applied/api.md — правила API-слоя: клиенты, эндпоинты, обработка ошибок
- basics/typing.md — типизация запросов и ответов
## Шаги
1. Определи подход:
- Клиентские данные → SWR / хук
- Серверные данные → серверный компонент (RSC)
2. Опиши типы запроса и ответа.
3. Создай или расширь API-клиент (→ applied/api.md).
4. Создай хук для использования в компоненте (→ triggers/develop/create-hook.md).
## Смежные триггеры
- triggers/develop/create-hook.md — хук для запроса
- triggers/develop/create-component.md — компонент, использующий данные
## Проверь себя
- [ ] Типы запроса и ответа описаны
- [ ] Хук для использования в компоненте создан
- [ ] Обработка ошибок реализована

View File

@@ -0,0 +1,24 @@
---
title: Добавить зависимость
---
# Добавить зависимость
Инструкция по добавлению новой npm-зависимости в проект. Проверь допустимость перед установкой.
## Прочитай перед началом
- basics/tech-stack.md — разрешённый стек, допустимые библиотеки
## Шаги
1. Проверь, что библиотека не дублирует уже используемую (→ basics/tech-stack.md).
2. Проверь, что библиотека входит в разрешённый список или обоснуй необходимость.
3. Установи как `dependency` или `devDependency` в зависимости от назначения.
## Проверь себя
- [ ] Библиотека не дублирует уже используемую
- [ ] Библиотека входит в разрешённый список (→ basics/tech-stack.md)

View File

@@ -0,0 +1,28 @@
---
title: Подключить шрифт
---
# Подключить шрифт
Инструкция по подключению и настройке шрифта в проекте.
## Прочитай перед началом
- applied/fonts.md — правила подключения шрифтов: форматы, загрузка, CSS-переменные
## Шаги
1. Подготовь файлы шрифта (woff2).
2. Подключи шрифт по правилам (→ applied/fonts.md).
3. Зарегистрируй CSS-переменную для шрифта.
## Смежные триггеры
- triggers/develop/style-component.md — использование шрифта в стилях
## Проверь себя
- [ ] Файл шрифта в формате woff2
- [ ] CSS-переменная для шрифта зарегистрирована

View File

@@ -0,0 +1,29 @@
---
title: Добавить иконку
---
# Добавить иконку
Инструкция по добавлению SVG-иконки в проект через спрайт-систему.
## Прочитай перед началом
- applied/svg-sprites.md — правила SVG-спрайтов: структура, именование, использование
## Шаги
1. Подготовь SVG-файл: убери лишние атрибуты, оптимизируй.
2. Добавь SVG в спрайт по правилам (→ applied/svg-sprites.md).
3. Используй иконку в компоненте через компонент-обёртку.
## Смежные триггеры
- triggers/develop/create-component.md — если нужен компонент-обёртка для иконки
- triggers/develop/style-component.md — стилизация иконки (размер, цвет)
## Проверь себя
- [ ] SVG оптимизирован — убраны лишние атрибуты
- [ ] Иконка добавлена в спрайт по правилам (→ applied/svg-sprites.md)

View File

@@ -0,0 +1,30 @@
---
title: Добавить изображение
---
# Добавить изображение
Инструкция по добавлению и использованию растровых изображений в проекте.
## Прочитай перед началом
- applied/images-sprites.md — правила работы с изображениями: оптимизация, форматы, подключение
## Шаги
1. Определи тип изображения:
- Статическое (логотип, декор) → `public/`
- Динамическое (контентное) → URL из API
2. Оптимизируй изображение (формат, размер, сжатие).
3. Подключи в компоненте по правилам (→ applied/images-sprites.md).
## Смежные триггеры
- triggers/develop/create-component.md — если нужен компонент-обёртка для изображения
## Проверь себя
- [ ] Изображение оптимизировано (формат, размер, сжатие)
- [ ] Подключено по правилам (→ applied/images-sprites.md)

View File

@@ -0,0 +1,28 @@
---
title: Добавить перевод
---
# Добавить перевод
Инструкция по добавлению локализации: создание ключей перевода и подключение в компоненте.
## Прочитай перед началом
- applied/localization.md — правила локализации: namespace, ключи, форматирование
## Шаги
1. Определи namespace для переводов (→ applied/localization.md).
2. Добавь ключи перевода в файлы локализации.
3. Подключи переводы в компоненте (→ applied/localization.md).
## Смежные триггеры
- triggers/develop/create-component.md — если компонент ещё не создан
## Проверь себя
- [ ] Ключи перевода добавлены в файлы локализации
- [ ] Namespace определён (→ applied/localization.md)

View File

@@ -0,0 +1,27 @@
---
title: Добавить видео
---
# Добавить видео
Инструкция по встраиванию видео в проект.
## Прочитай перед началом
- applied/video.md — правила работы с видео: форматы, плеер, оптимизация
## Шаги
1. Определи тип видео:
- Локальное → `public/`
- Внешнее (YouTube, Vimeo) → embed
2. Подключи видео по правилам (→ applied/video.md).
## Смежные триггеры
- triggers/develop/create-component.md — если нужен компонент-обёртка для видео
## Проверь себя
- [ ] Видео подключено по правилам (→ applied/video.md)

View File

@@ -0,0 +1,31 @@
---
title: Подключить стор к компоненту
---
# Подключить стор к компоненту
Инструкция по подключению стора к React-компоненту.
## Прочитай перед началом
- applied/stores.md — правила сторов: подписка, селекторы
## Шаги
1. Определи нужен ли стор:
- Локальное состояние → `useState` / `useReducer`
- Глобальное состояние → стор
2. Если стор не существует — создай его (→ triggers/develop/create-store.md).
3. Подключи стор в компоненте через селектор (→ applied/stores.md).
## Смежные триггеры
- triggers/develop/create-store.md — создание нового стора
- triggers/develop/create-hook.md — хук-обёртка над стором
## Проверь себя
- [ ] Используется селектор, а не подписка на весь стор
- [ ] Выбор локальное/глобальное состояние обоснован

View File

@@ -0,0 +1,38 @@
---
title: Создать компонент
---
# Создать компонент
Инструкция по созданию React-компонента в проекте. Определи слой, сгенерируй из шаблона, реализуй по правилам.
## Прочитай перед началом
- applied/components.md — правила компонентов: структура файлов, пропсы, документирование
- basics/naming.md — именование файла и экспортов
## Шаги
1. Определи слой компонента по его назначению (→ basics/architecture.md):
- `shared/ui/` — переиспользуемый UI без бизнес-логики
- `features/` — фича с бизнес-логикой
- `widgets/` — композиция фичей и сущностей
- `entities/` — бизнес-сущность с UI
2. Сгенерируй модуль из шаблона (→ triggers/develop/generate-module.md).
3. Реализуй компонент по правилам (→ applied/components.md).
4. Если нужны стили — см. triggers/develop/style-component.md.
## Смежные триггеры
- triggers/develop/style-component.md — стилизация компонента
- triggers/develop/add-icon.md — добавление иконки в компонент
- triggers/develop/generate-module.md — генерация из шаблона
## Проверь себя
- [ ] Компонент создан из шаблона, не вручную
- [ ] Файл и экспорт именованы по конвенции (→ basics/naming.md)
- [ ] Пропсы типизированы (→ basics/typing.md)

View File

@@ -0,0 +1,34 @@
---
title: Создать сущность
---
# Создать сущность
Инструкция по созданию модуля на слое `entities/`. Сущность — бизнес-объект с UI-представлением и моделью данных.
## Прочитай перед началом
- basics/architecture.md — слои и зависимости
- applied/components.md — правила компонентов
## Шаги
1. Сгенерируй модуль из шаблона `entity` (→ triggers/develop/generate-module.md).
2. Определи модель данных — типы в `model/`.
3. Реализуй UI-компонент сущности.
4. Настрой публичный API — экспорт через `index.ts`.
## Смежные триггеры
- triggers/develop/create-component.md — UI-компонент сущности
- triggers/develop/create-store.md — стор для сущности
- triggers/develop/generate-module.md — генерация из шаблона
## Проверь себя
- [ ] Сущность создана из шаблона `entity`
- [ ] Модель данных определена — типы в `model/`
- [ ] Публичный API настроен — экспорт через `index.ts`

View File

@@ -0,0 +1,37 @@
---
title: Создать фичу
---
# Создать фичу
Инструкция по созданию модуля на слое `features/`. Фича — самодостаточный блок с бизнес-логикой и UI.
## Прочитай перед началом
- basics/architecture.md — слои и зависимости
- applied/components.md — правила компонентов
## Шаги
1. Сгенерируй модуль из шаблона `feature` (→ triggers/develop/generate-module.md).
2. Реализуй компонент фичи (→ applied/components.md).
3. Если нужен стор — создай в `model/` (→ triggers/develop/create-store.md).
4. Если нужны хуки — создай в `model/` (→ triggers/develop/create-hook.md).
5. Настрой публичный API — экспорт через `index.ts`.
## Смежные триггеры
- triggers/develop/create-component.md — компонент внутри фичи
- triggers/develop/create-store.md — стор для фичи
- triggers/develop/create-hook.md — хук для фичи
- triggers/develop/generate-module.md — генерация из шаблона
## Проверь себя
- [ ] Фича создана из шаблона `feature`
- [ ] Публичный API настроен — экспорт через `index.ts`
- [ ] Нет зависимостей от других фичей (→ basics/architecture.md)

View File

@@ -0,0 +1,36 @@
---
title: Создать хук
---
# Создать хук
Инструкция по созданию кастомного React-хука. Определи где он живёт, реализуй по правилам.
## Прочитай перед началом
- applied/hooks.md — правила хуков
- basics/naming.md — именование (префикс `use`)
- basics/typing.md — типизация параметров и возврата
## Шаги
1. Определи область хука:
- Утилитарный (не привязан к бизнес-логике) → `shared/hooks/`
- Привязан к фиче/сущности → `model/` внутри модуля
2. Создай файл с именем `use-{name}.ts`.
3. Реализуй хук по правилам (→ applied/hooks.md).
4. Экспортируй через публичный API модуля.
## Смежные триггеры
- triggers/develop/create-component.md — если хук используется в новом компоненте
- triggers/develop/connect-store.md — если хук подключает стор
## Проверь себя
- [ ] Имя начинается с `use` (→ basics/naming.md)
- [ ] Параметры и возвращаемое значение типизированы
- [ ] Хук экспортирован через публичный API модуля

View File

@@ -0,0 +1,36 @@
---
title: Создать стор
---
# Создать стор
Инструкция по созданию стора для управления состоянием. Определи область, сгенерируй из шаблона.
## Прочитай перед началом
- applied/stores.md — правила сторов
- basics/naming.md — именование
- basics/typing.md — типизация состояния и экшенов
## Шаги
1. Определи область стора:
- Глобальный → `shared/model/`
- Привязан к фиче/сущности → `model/` внутри модуля
2. Сгенерируй из шаблона `store` (→ triggers/develop/generate-module.md).
3. Реализуй стор по правилам (→ applied/stores.md).
4. Экспортируй через публичный API модуля.
## Смежные триггеры
- triggers/develop/connect-store.md — подключение стора к компоненту
- triggers/develop/generate-module.md — генерация из шаблона
## Проверь себя
- [ ] Стор создан из шаблона `store`
- [ ] Состояние и экшены типизированы
- [ ] Стор экспортирован через публичный API модуля

View File

@@ -0,0 +1,32 @@
---
title: Создать виджет
---
# Создать виджет
Инструкция по созданию модуля на слое `widgets/`. Виджет — композиция фичей и сущностей.
## Прочитай перед началом
- basics/architecture.md — слои и зависимости
- applied/components.md — правила компонентов
## Шаги
1. Сгенерируй модуль из шаблона `widget` (→ triggers/develop/generate-module.md).
2. Скомпонуй виджет из существующих фичей и сущностей.
3. Настрой публичный API — экспорт через `index.ts`.
## Смежные триггеры
- triggers/develop/create-feature.md — если нужна новая фича для виджета
- triggers/develop/create-component.md — UI-компоненты внутри виджета
- triggers/develop/generate-module.md — генерация из шаблона
## Проверь себя
- [ ] Виджет создан из шаблона `widget`
- [ ] Композиция из существующих фичей/сущностей, не дублирует логику
- [ ] Публичный API настроен — экспорт через `index.ts`

View File

@@ -0,0 +1,37 @@
---
title: Сгенерировать модуль из шаблона
---
# Сгенерировать модуль из шаблона
Инструкция по генерации модуля из шаблонов `.templates/`. Ручное создание файловой структуры запрещено.
## Прочитай перед началом
- applied/templates-generation.md — шаблоны, синтаксис, инструменты генерации
## Шаги
1. Определи тип модуля и шаблон (→ applied/templates-generation.md):
- Компонент → `component`
- Фича → `feature`
- Виджет → `widget`
- Сущность → `entity`
- Layout → `layout`
- Экран → `screen`
- Стор → `store`
2. Запусти генерацию (→ applied/templates-generation.md).
3. Если подходящего шаблона нет — сначала создай шаблон, затем генерируй.
## Смежные триггеры
- triggers/develop/create-component.md — после генерации компонента
- triggers/develop/create-feature.md — после генерации фичи
- triggers/develop/create-store.md — после генерации стора
## Проверь себя
- [ ] Модуль создан из шаблона, не вручную
- [ ] Выбран правильный шаблон для типа модуля (→ applied/templates-generation.md)

View File

@@ -0,0 +1,24 @@
---
title: Настроить VS Code
---
# Настроить VS Code
Инструкция по настройке VS Code для работы с проектом.
## Прочитай перед началом
- applied/vscode.md — настройки, расширения, сниппеты
## Шаги
1. Установи рекомендованные расширения (→ applied/vscode.md).
2. Проверь настройки `.vscode/settings.json`.
3. Настрой сниппеты при необходимости.
## Проверь себя
- [ ] Рекомендованные расширения установлены
- [ ] Настройки `.vscode/settings.json` проверены

View File

@@ -0,0 +1,35 @@
---
title: Стилизовать компонент
---
# Стилизовать компонент
Инструкция по выбору подхода к стилизации и написанию стилей для компонента.
## Прочитай перед началом
- applied/styles.md — правила CSS: PostCSS Modules, токены, медиа-запросы
## Шаги
1. Определи подход (→ applied/styles.md):
- Mantine-компонент → используй пропсы Mantine, не пиши CSS
- CSS-токены достаточно → используй токены
- Нужна кастомная стилизация → PostCSS Modules
2. Создай файл стилей `{component-name}.module.css` рядом с компонентом.
3. Напиши стили по правилам (→ applied/styles.md).
4. Подключи стили в компоненте через `cl()`.
## Смежные триггеры
- triggers/develop/create-component.md — если компонент ещё не создан
- triggers/develop/add-icon.md — если нужна иконка в компоненте
## Проверь себя
- [ ] Приоритет стилизации соблюдён: Mantine → токены → PostCSS Modules
- [ ] Нет инлайн-стилей и магических значений
- [ ] Файл стилей именован `{component-name}.module.css`

96
src/nextjs/DEVELOP.md Normal file
View File

@@ -0,0 +1,96 @@
# Стайлгайд — Разработка
Правила и стандарты разработки на Next.js и TypeScript.
## Как работать
1. **Изучи обязательные правила** (таблица ниже) — они действуют при любой задаче.
2. Найди задачу в таблицах триггеров → открой триггер.
3. Триггер укажет какие прикладные разделы прочитать и какие шаги выполнить.
4. Перед каждой подзадачей возвращайся к триггерам — проверяй, нет ли готового.
5. Если триггера нет — ищи прикладной раздел по области задачи.
---
## Обязательные правила
Прочитай эти разделы **до начала работы**. Соблюдай при написании любого кода.
| Раздел | Файл | Что внутри |
|--------|------|------------|
| Структура проекта | applied/project-structure.md | Организация папок и файлов |
| Архитектура | basics/architecture.md | SLM Design: слои, модули, сегменты |
| Стиль кода | basics/code-style.md | Форматирование, импорты, отступы |
| Именование | basics/naming.md | Имена файлов, переменных, событий |
| Типизация | basics/typing.md | type vs interface, generic, any/unknown |
| Документирование | basics/documentation.md | JSDoc для функций, компонентов, типов |
| Технологии | basics/tech-stack.md | Допустимые библиотеки и зависимости |
---
## Прикладные разделы
Справочник по областям. Читай тот раздел, который относится к текущей задаче.
| Область | Файл | Когда читать |
|---------|------|--------------|
| Компоненты | applied/components.md | Создание или редактирование React-компонентов |
| Стили | applied/styles.md | CSS Modules, PostCSS, переменные, медиа-запросы |
| Файлы роутинга | applied/page-level.md | page.tsx, layout.tsx, error.tsx, not-found.tsx |
| Шаблоны и генерация | applied/templates-generation.md | Генерация кода из шаблонов |
| Настройка VS Code | applied/vscode.md | Расширения, settings.json, сниппеты |
| SVG-спрайты | applied/svg-sprites.md | Работа с SVG-иконками и спрайтами |
| Хуки | applied/hooks.md | Создание и использование кастомных хуков *(в разработке)* |
| Сторы | applied/stores.md | Глобальное состояние, Zustand *(в разработке)* |
| API | applied/api.md | Запросы, клиенты, обработка ответов *(в разработке)* |
| Локализация | applied/localization.md | i18next, переводы *(в разработке)* |
| Изображения | applied/images-sprites.md | Подключение и оптимизация изображений *(в разработке)* |
| Шрифты | applied/fonts.md | Подключение и настройка шрифтов *(в разработке)* |
| Видео | applied/video.md | Встраивание видео *(в разработке)* |
---
## Триггеры
Пошаговые инструкции. Найди задачу → открой триггер → выполняй по шагам.
### Создание
| Задача | Триггер | Описание |
|--------|---------|----------|
| Создать компонент | triggers/develop/create-component.md | Переиспользуемый UI-элемент без бизнес-логики |
| Создать фичу | triggers/develop/create-feature.md | Самодостаточный блок с бизнес-логикой и UI |
| Создать виджет | triggers/develop/create-widget.md | Композиция нескольких фичей и сущностей |
| Создать сущность | triggers/develop/create-entity.md | Бизнес-объект с моделью данных и UI-представлением |
| Создать хук | triggers/develop/create-hook.md | Кастомный React-хук с переиспользуемой логикой |
| Создать стор | triggers/develop/create-store.md | Глобальное или модульное состояние через Zustand |
| Создать страницу | triggers/develop/create-page.md | Новый route в Next.js — экран + page.tsx |
| Создать layout | triggers/develop/create-layout.md | Общая обёртка layout.tsx для группы страниц |
| Создать проект | triggers/develop/create-project.md | Инициализация нового проекта из шаблона |
| Сгенерировать модуль | triggers/develop/generate-module.md | Создание модуля из шаблонов `.templates/` |
### Стилизация и ресурсы
| Задача | Триггер | Описание |
|--------|---------|----------|
| Стилизовать компонент | triggers/develop/style-component.md | Выбор подхода и написание CSS для компонента |
| Добавить иконку | triggers/develop/add-icon.md | SVG-иконка через спрайт-систему |
| Добавить изображение | triggers/develop/add-image.md | Растровое изображение (png, jpg, webp) |
| Добавить видео | triggers/develop/add-video.md | Встраивание видео на страницу |
| Подключить шрифт | triggers/develop/add-font.md | Подключение нового шрифта в проект |
### Данные и состояние
| Задача | Триггер | Описание |
|--------|---------|----------|
| Добавить API-запрос | triggers/develop/add-api-request.md | Клиентский запрос данных через SWR |
| Подключить стор | triggers/develop/connect-store.md | Подключение существующего стора к компоненту |
| Серверные данные (RSC) | triggers/develop/add-server-data.md | Получение данных в серверных компонентах |
### Инфраструктура
| Задача | Триггер | Описание |
|--------|---------|----------|
| Добавить перевод | triggers/develop/add-localization.md | Ключи перевода и подключение i18next |
| Добавить зависимость | triggers/develop/add-dependency.md | Подключение новой npm-библиотеки |
| Настроить VS Code | triggers/develop/setup-vscode.md | Расширения, настройки редактора, сниппеты |

43
src/nextjs/REVIEW.md Normal file
View File

@@ -0,0 +1,43 @@
# Стайлгайд — Ревью
Правила проверки кода на Next.js и TypeScript. Читай перед ревью PR.
## Как пользоваться
1. Определи какие области затронуты в PR.
2. Открой соответствующие разделы из таблиц ниже.
3. Проверь код на соответствие правилам.
## Что проверять всегда
Базовые правила — применимы к любому коду в PR.
| Что проверить | Раздел |
|---------------|--------|
| Форматирование, импорты, early return | basics/code-style.md |
| Имена файлов, переменных, компонентов | basics/naming.md |
| Нет any, правильный type/interface | basics/typing.md |
| JSDoc на публичных функциях | basics/documentation.md |
| Правильный слой, нет запрещённых зависимостей | basics/architecture.md |
| Нет неразрешённых библиотек | basics/tech-stack.md |
## Что проверять по области
Открой раздел, если PR затрагивает соответствующую область.
| Область | Раздел |
|---------|--------|
| React-компоненты | applied/components.md |
| CSS / стили | applied/styles.md |
| Шаблоны генерации | applied/templates-generation.md |
| Хуки | applied/hooks.md |
| Сторы | applied/stores.md |
| API-слой | applied/api.md |
| Шрифты | applied/fonts.md |
| Переводы | applied/localization.md |
| Изображения | applied/images-sprites.md |
| SVG-иконки | applied/svg-sprites.md |
| Видео | applied/video.md |
| Настройки VS Code | applied/vscode.md |
| page.tsx, layout.tsx | applied/page-level.md |
| Структура папок | applied/project-structure.md |

View File

@@ -0,0 +1,68 @@
---
title: Файлы роутинга
scope: applied
keywords: [page.tsx, layout.tsx, error.tsx, not-found.tsx, loading.tsx, App Router, metadata]
when: "Работа с файлами роутинга Next.js App Router: page, layout, error, not-found"
---
# Файлы роутинга
Правила для специальных файлов App Router (`page.tsx`, `layout.tsx`, `error.tsx`, `not-found.tsx` и др.) — чем наш подход отличается от дефолтного.
## Что нужно знать
Страница в проекте — это два файла: экран в `src/screens/` (вся логика, стили, зависимости) и `page.tsx` в `src/app/` (точка входа для роутинга Next.js). Экран генерируется из шаблона, `page.tsx` создаётся вручную.
## Организация
- `page.tsx` — тонкий файл: только `metadata` и рендер экрана. Логика, стили и зависимости живут в экране, не в `page.tsx`.
- `error.tsx` и `not-found.tsx` делегируют разметку экранам по тому же принципу.
- `layout.tsx` — точка подключения провайдеров и глобальных стилей. Вёрстка layout-обёрток выносится в слой `layouts/`.
- Стили в файлах роутинга не используются — стилизация только внутри вызываемых компонентов.
## Реализация
- Каждый `page.tsx` экспортирует `metadata` с `title` — он подставляется в шаблон корневого layout (`%s | App`).
- Корневой `layout.tsx` задаёт `metadata` с `title.template`, `description`, `metadataBase` и OpenGraph-настройками.
## Примеры
`src/app/profile/[id]/page.tsx`
```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,11 +1,12 @@
--- ---
title: Структура проекта title: Структура проекта
description: Из чего состоит проект и где что лежит. scope: applied
keywords: [структура проекта, папки, src/app, src/shared, FSD, Next.js структура]
when: "Организация папок и файлов в Next.js проекте"
--- ---
# Структура проекта # Структура проекта
Из чего состоит проект и где что лежит. Раздел описывает расположение файлов и папок в проекте Next.js (App Router).
## Корень репозитория ## Корень репозитория
@@ -41,27 +42,25 @@ public/
```text ```text
src/ src/
├── app/ # Роутинг Next.js и точка входа приложения ├── app/ # Роутинг Next.js, провайдеры, глобальные стили
├── layouts/ # Каркасы страниц (header, footer, sidebar) ├── screens/ # Собраные страницы (UI)
├── screens/ # Контент конкретной страницы ├── layouts/ # Шаблоны
├── widgets/ # Составные блоки интерфейса, не привязанные к домену ├── widgets/ # Крупные самостоятельные блоки интерфейса
├── business/ # Бизнес-домены (auth, catalog, orders) ├── features/ # Пользовательские сценарии
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры) ├── entities/ # Бизнес-сущности
── ui/ # UI-кит без бизнес-логики ── shared/ # Переиспользуемый код (UI, утилиты, типы и др.)
└── shared/ # Общие ресурсы (утилиты, типы, стили)
``` ```
Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture/). Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture).
### Папка `app/` ### Папка `app/`
Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты). Совмещает два слоя: инициализацию приложения по FSD (провайдеры, глобальные стили) и файловый роутинг 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 # Главная страница
``` ```
@@ -76,11 +75,12 @@ src/app/
├── screen/ # Шаблон экрана ├── screen/ # Шаблон экрана
├── layout/ # Шаблон layout ├── layout/ # Шаблон layout
├── widget/ # Шаблон виджета ├── widget/ # Шаблон виджета
├── module/ # Шаблон бизнес-модуля ├── feature/ # Шаблон фичи
├── entity/ # Шаблон сущности
└── store/ # Шаблон стора └── store/ # Шаблон стора
``` ```
Подробнее о генерации описано в разделе [Шаблоны генерации](/docs/applied/templates/templates-intro). Подробнее о генерации описано в разделе [Шаблоны и генерация кода](./templates-generation).
## Конфигурационные файлы ## Конфигурационные файлы

View File

@@ -0,0 +1,33 @@
---
title: Добавить серверные данные
---
# Добавить серверные данные
Инструкция по получению данных в серверных компонентах (RSC) Next.js.
## Прочитай перед началом
- applied/page-level.md — серверные компоненты в App Router
- applied/api.md — API-клиенты
## Шаги
1. Определи где получать данные:
- В `page.tsx` / `layout.tsx` → серверный fetch
- В клиентском компоненте → SWR (→ triggers/develop/add-api-request.md)
2. Создай или расширь серверный API-клиент.
3. Получи данные в серверном компоненте и передай через пропсы.
## Смежные триггеры
- triggers/develop/add-api-request.md — клиентские запросы (SWR)
- triggers/develop/create-page.md — серверный fetch в page.tsx
## Проверь себя
- [ ] Определён тип: серверный fetch или клиентский SWR
- [ ] Типы запроса и ответа описаны
- [ ] Данные передаются через пропсы, не через глобальное состояние

Some files were not shown because too many files have changed in this diff Show More