diff --git a/ai/nextjs-style-guide/MAP.md b/ai/nextjs-style-guide/MAP.md
index f4d196a..eeafb89 100644
--- a/ai/nextjs-style-guide/MAP.md
+++ b/ai/nextjs-style-guide/MAP.md
@@ -19,35 +19,28 @@
- [Документирование](./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/creating-project/from-template.md) — Создание нового проекта на основе готового шаблона.
+- [Создание проекта: По гайду вручную](./applied/creating-project/manual.md) — Поэтапное создание нового проекта без использования шаблона.
+- [Создание проекта: Чистый Next.js](./applied/creating-project/nextjs.md) — Установка Next.js без лишнего шаблона — голый каркас под дальнейшую сборку.
- [Структура проекта](./applied/project-structure.md) — Из чего состоит проект и где что лежит.
- [Страницы](./applied/page-level.md) — Как работать со страницами и другими файлами роутинга Next.js App Router.
- [Компонент](./applied/component.md) — Как создавать React-компоненты внутри SLM-модулей.
- [Модуль](./applied/module.md) — Как создавать и организовывать SLM-модули в проекте.
+- [REST-клиент](./applied/rest-client/index.md) — Настройка REST-клиента сервиса для работы с внешним API.
+- [REST-клиент: Настройка REST-клиента](./applied/rest-client/setup/index.md) — Из чего состоит REST-клиент и что подготовить перед использованием API.
+- [REST-клиент: Автогенерация REST-клиента](./applied/rest-client/setup/auto.md) — Генерация REST-клиента из OpenAPI-спецификации.
+- [REST-клиент: Ручное создание REST-клиента](./applied/rest-client/setup/manual.md) — Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
+- [REST-клиент: GET-хуки REST-клиента](./applied/rest-client/setup/hooks.md) — Прозрачные SWR-обёртки над GET-методами REST-клиента.
+- [REST-клиент: Использование REST-клиента](./applied/rest-client/usage.md) — Как вызывать готовый REST-клиент в серверном коде и submit-функциях.
+- [Получение данных](./applied/data-fetch/index.md) — Как получать данные с учётом рендера страницы.
+- [Получение данных: Серверный await](./applied/data-fetch/server-await.md) — Получение REST-данных на сервере до первого HTML.
+- [Получение данных: Параллельные серверные запросы](./applied/data-fetch/parallel-server-requests.md) — Как запускать независимые REST-запросы на сервере без waterfall.
+- [Получение данных: Передача промиса ниже](./applied/data-fetch/pass-promise-down.md) — Как запускать серверный REST-запрос выше и ожидать его во вложенном server-компоненте.
+- [Получение данных: Начальные данные для клиентских хуков](./applied/data-fetch/client-hooks-initial-data.md) — Как дать клиентским GET-хукам начальные REST-данные.
+- [Получение данных: Клиентский GET-хук](./applied/data-fetch/client-get-hook.md) — Получение REST-данных в Client Components через готовые GET-хуки REST-клиента.
+- [Получение данных: Business-композиция](./applied/data-fetch/business-composition.md) — Когда REST-данные нужно объединить или интерпретировать в бизнес-модуле.
- [Стили: Настройка](./applied/styles/styles-setup.md) — Подготовка стилевой основы проекта: токены, медиа-запросы, глобальные стили.
- [Стили: Использование](./applied/styles/styles-usage.md) — Как пишутся стили в проекте.
- [SVG-спрайты](./applied/svg-sprites/svg-sprites-intro.md) — Что такое SVG-спрайты и какие проблемы они решают.
diff --git a/ai/nextjs-style-guide/VERSION b/ai/nextjs-style-guide/VERSION
index 138d54c..1fbf5a5 100644
--- a/ai/nextjs-style-guide/VERSION
+++ b/ai/nextjs-style-guide/VERSION
@@ -1,2 +1,2 @@
-eadc462
-2026-05-08T04:14:35.127Z
+8231356
+2026-05-08T16:14:00.876Z
diff --git a/ai/nextjs-style-guide/applied/biome.md b/ai/nextjs-style-guide/applied/biome.md
index b8a8483..9551821 100644
--- a/ai/nextjs-style-guide/applied/biome.md
+++ b/ai/nextjs-style-guide/applied/biome.md
@@ -29,7 +29,7 @@ keywords: [biome, линтер, форматтер, lint, format, biome.json, "@
В корне появится `biome.json` с дефолтными настройками.
-3. Привести `biome.json` к стандартному виду — добавить override для `*.css` (см. «Стандартный `biome.json`»). Делается сразу после `init`, до первого запуска `lint`/`check`.
+3. Привести `biome.json` к стандартному виду (см. «Стандартный `biome.json`»). Делается сразу после `init`, до первого запуска `lint`/`check`.
4. Добавить скрипты в `package.json`:
@@ -51,30 +51,63 @@ keywords: [biome, линтер, форматтер, lint, format, biome.json, "@
## Стандартный `biome.json`
-Дефолтный `biome.json`, созданный `biome init`, кастомизируется ровно одним блоком — `overrides` для `*.css` с отключённым правилом `suspicious/noUnknownAtRules`. Этот override **обязателен по умолчанию во всех проектах**, независимо от того, подключены ли уже стили: проектный CSS-стек использует `@custom-media` и другие нестандартные at-правила, которые Biome не распознаёт; без override `npm run lint` падает.
+Дефолтный `biome.json`, созданный `biome init`, заменяется стандартным конфигом проекта.
-Фрагмент, который добавляется в `biome.json`:
+Стандартный `biome.json`:
```jsonc
{
- "overrides": [
- {
- "includes": ["**/*.css"],
- "linter": {
- "rules": {
- "suspicious": {
- "noUnknownAtRules": "off"
- }
- }
+ "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ },
+ "files": {
+ "ignoreUnknown": true,
+ "includes": ["**", "!node_modules", "!.next", "!dist", "!build", "!.templates", "!src/infra/**/generated"]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "indentWidth": 2,
+ "lineWidth": 120
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "single",
+ "jsxQuoteStyle": "double"
+ }
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "suspicious": {
+ "noUnknownAtRules": "off"
+ },
+ "correctness": {
+ "noUnknownMediaFeatureName": "off"
+ }
+ },
+ "domains": {
+ "next": "recommended",
+ "react": "recommended"
+ }
+ },
+ "assist": {
+ "actions": {
+ "source": {
+ "organizeImports": "on"
}
}
- ]
+ }
}
```
-Если в `biome.json` уже есть массив `overrides` — добавить элемент в него; не дублировать массив.
+`src/infra/**/generated` исключается из Biome, потому что generated-файлы не правятся руками. При этом generated-файлы остаются в git.
-Прочая настройка правил Biome — отдельная задача, не входит в стандартный канон.
+Правила `suspicious/noUnknownAtRules` и `correctness/noUnknownMediaFeatureName` отключены, потому что проектный CSS-стек использует `@custom-media` и другие конструкции, которые Biome может не распознавать.
## Интеграция с VS Code
diff --git a/ai/nextjs-style-guide/creating-project/from-template.md b/ai/nextjs-style-guide/applied/creating-project/from-template.md
similarity index 98%
rename from ai/nextjs-style-guide/creating-project/from-template.md
rename to ai/nextjs-style-guide/applied/creating-project/from-template.md
index c549640..3a760e2 100644
--- a/ai/nextjs-style-guide/creating-project/from-template.md
+++ b/ai/nextjs-style-guide/applied/creating-project/from-template.md
@@ -14,11 +14,11 @@ keywords: [создать проект из шаблона, шаблон, templa
- **Стек:** Next.js (App Router), TypeScript, React.
- **Архитектура:** структура папок по SLM, алиасы импортов.
-- **Качество кода:** Biome (линтер и форматер), настройки VS Code.
+- **Качество кода:** Biome (линтер и форматтер), настройки VS Code.
- **Стили:** PostCSS Modules с плагинами, токены, медиа-брейкпоинты.
- **Ассеты:** генерация SVG-спрайтов.
- **Кодогенерация:** шаблоны для страниц, компонентов, хуков, сторов.
-в
+
## Установка
1. Склонировать шаблон в родительском каталоге будущего проекта:
diff --git a/ai/nextjs-style-guide/creating-project/manual.md b/ai/nextjs-style-guide/applied/creating-project/manual.md
similarity index 85%
rename from ai/nextjs-style-guide/creating-project/manual.md
rename to ai/nextjs-style-guide/applied/creating-project/manual.md
index d345a50..17d2835 100644
--- a/ai/nextjs-style-guide/creating-project/manual.md
+++ b/ai/nextjs-style-guide/applied/creating-project/manual.md
@@ -13,19 +13,19 @@ keywords: [создать проект, новый проект, с нуля, in
| Компонент | Роль | Раздел |
|-----------|------|--------|
| Next.js | Фреймворк (роутинг, сборка, SSR) | [Next.js](./nextjs.md) |
-| Алиасы | Импорты по слоям SLM | [Алиасы](../applied/aliases.md) |
-| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](../applied/biome.md) |
-| Стили | Глобальные токены и breakpoints | [Стили](../applied/styles/styles-setup.md) |
-| PostCSS | CSS-процессор для custom-media и вложенности | [PostCSS](../applied/postcss.md) |
-| SVG-спрайты | Иконки через ``, управление цветом | [SVG-спрайты](../applied/svg-sprites/svg-sprites-setup.md) |
-| VS Code | Настройки редактора и расширения | [VS Code](../applied/vscode.md) |
-| Шаблоны генерации | `.templates/` для `@gromlab/create` | [Шаблоны генерации](../applied/templates/templates-setup.md) |
+| Алиасы | Импорты по слоям SLM | [Алиасы](../aliases.md) |
+| Biome | Линтер и форматтер (замена ESLint + Prettier) | [Biome](../biome.md) |
+| Стили | Глобальные токены и breakpoints | [Стили](../styles/styles-setup.md) |
+| PostCSS | CSS-процессор для custom-media и вложенности | [PostCSS](../postcss.md) |
+| SVG-спрайты | Иконки через ``, управление цветом | [SVG-спрайты](../svg-sprites/svg-sprites-setup.md) |
+| VS Code | Настройки редактора и расширения | [VS Code](../vscode.md) |
+| Шаблоны генерации | `.templates/` для `@gromlab/create` | [Шаблоны генерации](../templates/templates-setup.md) |
Убрать компонент из состава — значит согласованно отказаться от части стайлгайда. Частичные проекты возможны (только Next.js, Next.js + стили и т.п.), но не являются эталоном.
## Канон раскладки
-В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](../applied/project-structure.md), [Архитектура](../basics/architecture/index.md)).
+В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](../project-structure.md), [Архитектура](../../basics/architecture/index.md)).
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
@@ -43,43 +43,43 @@ keywords: [создать проект, новый проект, с нуля, in
Заменить дефолтный `"@/*"` в `tsconfig.json` на канонический список из восьми слой-префиксов.
-См. [Алиасы](../applied/aliases.md).
+См. [Алиасы](../aliases.md).
### 3. Biome
Линтер и форматтер. Подключается **до** написания кода, иначе в проекте копятся несогласованные правки.
-См. [Biome](../applied/biome.md).
+См. [Biome](../biome.md).
### 4. Стили (базовая инфраструктура)
Файлы `variables.css`, `media.css`, `global.css` в `src/shared/styles/` и подключение `global.css` в `src/app/layout.tsx`. CSS-процессор на этом шаге не ставится.
-См. [Стили](../applied/styles/styles-setup.md).
+См. [Стили](../styles/styles-setup.md).
### 5. PostCSS
CSS-процессор поверх базовых стилей: `@custom-media`, вложенность, autoprefixer. Ставится **только после шага 4** — опирается на `src/shared/styles/media.css`.
-См. [PostCSS](../applied/postcss.md).
+См. [PostCSS](../postcss.md).
### 6. SVG-спрайты
Пакет `@gromlab/svg-sprites`, генерация спрайт-файла и React-компонента ``.
-См. [SVG-спрайты](../applied/svg-sprites/svg-sprites-setup.md).
+См. [SVG-спрайты](../svg-sprites/svg-sprites-setup.md).
### 7. VS Code
Расширения и настройки редактора. Опирается на установленный Biome (форматирование при сохранении) и PostCSS (ассоциация `*.css`).
-См. [VS Code](../applied/vscode.md).
+См. [VS Code](../vscode.md).
### 8. Шаблоны генерации
Папка `.templates/` для генератора модулей `@gromlab/create`.
-См. [Шаблоны генерации](../applied/templates/templates-setup.md).
+См. [Шаблоны генерации](../templates/templates-setup.md).
## Правила
diff --git a/ai/nextjs-style-guide/creating-project/nextjs.md b/ai/nextjs-style-guide/applied/creating-project/nextjs.md
similarity index 92%
rename from ai/nextjs-style-guide/creating-project/nextjs.md
rename to ai/nextjs-style-guide/applied/creating-project/nextjs.md
index 546f9f1..d22f331 100644
--- a/ai/nextjs-style-guide/creating-project/nextjs.md
+++ b/ai/nextjs-style-guide/applied/creating-project/nextjs.md
@@ -33,12 +33,12 @@ npx create-next-app@latest my-app \
| Флаг | Значение | Почему так |
|------|----------|------------|
-| `--typescript` | TS включён | Стайлгайд требует TypeScript ([Типизация](../basics/typing.md)) |
+| `--typescript` | TS включён | Стайлгайд требует TypeScript ([Типизация](../../basics/typing.md)) |
| `--app` | App Router | Pages Router не используется |
-| `--src-dir` | Код в `src/` | Архитектура SLM требует `src/` ([Структура проекта](../applied/project-structure.md)) |
-| `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](../applied/aliases.md)) |
-| `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](../applied/biome.md)) |
-| `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](../applied/styles/styles-usage.md)) |
+| `--src-dir` | Код в `src/` | Архитектура SLM требует `src/` ([Структура проекта](../project-structure.md)) |
+| `--import-alias "@/*"` | Placeholder | Требуется флагом; после установки `paths` полностью переписывается на слой-префиксы (см. [Алиасы](../aliases.md)) |
+| `--no-eslint` | ESLint не ставится | Линтер и форматтер — Biome ([Biome](../biome.md)) |
+| `--no-tailwind` | Tailwind не ставится | Стилизация — PostCSS Modules ([Стили](../styles/styles-usage.md)) |
| `--use-npm` | Пакетный менеджер — npm | Единый инструмент в проектах |
### 2. Очистить дефолтный шаблон
@@ -83,7 +83,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
### 3. Создать папку `src/shared/styles/`
-Глобальные стили в SLM-архитектуре живут в слое `shared`, а не в `src/app/` ([Структура проекта](../applied/project-structure.md)).
+Глобальные стили в SLM-архитектуре живут в слое `shared`, а не в `src/app/` ([Структура проекта](../project-structure.md)).
```bash
mkdir -p src/shared/styles
@@ -95,7 +95,7 @@ mkdir -p src/shared/styles
- **Конфликт с непустой директорией** — не удалять файлы пользователя автоматически. Ставить в подпапку или временно перенести посторонние файлы.
- **Отклонение от канонических флагов** (pnpm, Tailwind, ESLint и т.п.) — только осознанное, с пониманием, что стайлгайд этого не предусматривает.
-- **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](../applied/aliases.md)).
+- **Слои `src/`** не создавать авансом — появляются при первом модуле. Алиасы прописываются сразу на все восемь слоёв (см. [Алиасы](../aliases.md)).
- **`AGENTS.md` от Next.js** удаляется в шаге 2. Повторно не создаётся.
- **Biome, стили, PostCSS, SVG-спрайты, VS Code** — отдельные шаги установки, не в этом разделе.
diff --git a/ai/nextjs-style-guide/data/rest/strategies/business-composition.md b/ai/nextjs-style-guide/applied/data-fetch/business-composition.md
similarity index 88%
rename from ai/nextjs-style-guide/data/rest/strategies/business-composition.md
rename to ai/nextjs-style-guide/applied/data-fetch/business-composition.md
index b9250e5..09202e8 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/business-composition.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/business-composition.md
@@ -21,13 +21,13 @@ Business-композиция используется, когда просто
```ts
// src/business/pets/hooks/use-available-pets.hook.ts
-import { useGetPetList } from 'infra/pet-store-api'
+import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
/**
* Доменный список доступных питомцев.
*/
export const useAvailablePets = () => {
- const query = useGetPetList('available')
+ const query = useGetPetList({ status: StatusEnum.Available })
return {
...query,
@@ -42,15 +42,15 @@ export const useAvailablePets = () => {
```ts
// src/business/pets/hooks/use-pets-dashboard.hook.ts
-import { useGetPetList } from 'infra/pet-store-api'
+import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
/**
* Данные dashboard по питомцам.
*/
export const usePetsDashboard = () => {
- const availablePets = useGetPetList('available')
- const pendingPets = useGetPetList('pending')
- const soldPets = useGetPetList('sold')
+ const availablePets = useGetPetList({ status: StatusEnum.Available })
+ const pendingPets = useGetPetList({ status: StatusEnum.Pending })
+ const soldPets = useGetPetList({ status: StatusEnum.Sold })
return {
availablePets,
@@ -108,7 +108,7 @@ src/business/
```ts
// Плохо — business-смысл внутри infra-хука
-export const useGetPetList = (status: PetStatus) => {
+export const useGetPetList = (params?: FindPetsByStatusParams | null) => {
const query = useSWR(...)
return {
diff --git a/ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md b/ai/nextjs-style-guide/applied/data-fetch/client-get-hook.md
similarity index 89%
rename from ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md
rename to ai/nextjs-style-guide/applied/data-fetch/client-get-hook.md
index 6dde3e1..2525f2e 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/client-get-hook.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/client-get-hook.md
@@ -21,14 +21,13 @@ keywords: [rest, client components, swr, get-хук, client state]
'use client'
import { useState } from 'react'
-import { useGetPetList } from 'infra/pet-store-api'
-import type { PetStatus } from 'infra/pet-store-api'
+import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
-const statuses: PetStatus[] = ['available', 'pending', 'sold']
+const statuses = [StatusEnum.Available, StatusEnum.Pending, StatusEnum.Sold]
export function PetTabs() {
- const [status, setStatus] = useState('available')
- const { data: pets, isLoading, error } = useGetPetList(status)
+ const [status, setStatus] = useState(StatusEnum.Available)
+ const { data: pets, isLoading, error } = useGetPetList({ status })
return (
@@ -75,7 +74,7 @@ useEffect(() => {
// Плохо — useSWR в компоненте
const { data } = useSWR(
- ['pet-store-api', 'pet', 'list', status],
+ ['pet-store-api', `/pet/findByStatus?status=${status}`],
() => petStoreApi.pet.findPetsByStatus({ status }),
)
```
diff --git a/ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md b/ai/nextjs-style-guide/applied/data-fetch/client-hooks-initial-data.md
similarity index 87%
rename from ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md
rename to ai/nextjs-style-guide/applied/data-fetch/client-hooks-initial-data.md
index 610eb87..04845b3 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/client-hooks-initial-data.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/client-hooks-initial-data.md
@@ -31,8 +31,13 @@ keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize,
```ts
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
-export const getPetListKey = (status: PetStatus) =>
- ['pet-store-api', 'pet', 'list', status] as const
+export const getPetListKey = (params?: FindPetsByStatusParams | null) => {
+ if (!params?.status) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/findByStatus?status=${params.status}`] as const
+}
```
Ключ экспортируется из REST-модуля, потому что он нужен и GET-хуку, и серверному `SWRConfig fallback`.
@@ -46,6 +51,7 @@ import { SWRConfig, unstable_serialize } from 'swr'
import {
getPetListKey,
petStoreApi,
+ StatusEnum,
} from 'infra/pet-store-api'
type PetsLayoutProps = {
@@ -53,15 +59,14 @@ type PetsLayoutProps = {
}
export default async function PetsLayout({ children }: PetsLayoutProps) {
- const availablePetsPromise = petStoreApi.pet.findPetsByStatus({
- status: 'available',
- })
+ const params = { status: StatusEnum.Available }
+ const availablePetsPromise = petStoreApi.pet.findPetsByStatus(params)
return (
@@ -78,10 +83,12 @@ export default async function PetsLayout({ children }: PetsLayoutProps) {
```tsx
'use client'
-import { useGetPetList } from 'infra/pet-store-api'
+import { StatusEnum, useGetPetList } from 'infra/pet-store-api'
export function PetList() {
- const { data: pets, isLoading } = useGetPetList('available')
+ const { data: pets, isLoading } = useGetPetList({
+ status: StatusEnum.Available,
+ })
if (isLoading) return Загрузка...
@@ -100,6 +107,7 @@ export function PetList() {
## Что важно
- Ключ `fallback` должен совпадать с ключом GET-хука.
+- `fallback` использует ту же key-функцию и те же params, что и GET-хук.
- Серверный код вызывает метод клиента, а не GET-хук.
- Клиентский компонент вызывает GET-хук, а не `useSWR` напрямую.
- Эта стратегия не означает ручную работу с кешем в компонентах.
diff --git a/ai/nextjs-style-guide/data/rest/strategies/index.md b/ai/nextjs-style-guide/applied/data-fetch/index.md
similarity index 91%
rename from ai/nextjs-style-guide/data/rest/strategies/index.md
rename to ai/nextjs-style-guide/applied/data-fetch/index.md
index a7e6d34..4d60c90 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/index.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/index.md
@@ -1,14 +1,14 @@
---
-title: Стратегии получения данных
-description: Как выбрать получение REST-данных с учётом рендера страницы.
+title: Получение данных
+description: Как получать данные с учётом рендера страницы.
keywords: [rest, стратегии, render, isr, ssr, server components, swr, promise, business]
---
-# Стратегии получения данных
+# Получение данных
-Как выбрать получение REST-данных с учётом рендера страницы.
+Как получать данные с учётом рендера страницы.
-Перед выбором стратегии должен быть создан REST-клиент сервиса. Если клиента ещё нет, начните с раздела [Создание клиента](../clients/index.md).
+Перед выбором стратегии должен быть настроен REST-клиент сервиса. Если клиента ещё нет, начните с раздела [Настройка REST-клиента](../rest-client/setup/index.md).
## Сначала определите рендер страницы
@@ -86,8 +86,8 @@ useEffect(() => {
// Плохо — useSWR в компоненте
const { data } = useSWR(
- ['pet-store-api', 'pet', 'list', status],
- () => petStoreApi.pet.findPetsByStatus({ status }),
+ ['pet-store-api', '/pet/findByStatus?status=available'],
+ () => petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available }),
)
// Плохо — бизнес-флаг внутри GET-хука REST-клиента
diff --git a/ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md b/ai/nextjs-style-guide/applied/data-fetch/parallel-server-requests.md
similarity index 79%
rename from ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md
rename to ai/nextjs-style-guide/applied/data-fetch/parallel-server-requests.md
index fec2efb..d36beb0 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/parallel-server-requests.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/parallel-server-requests.md
@@ -17,13 +17,19 @@ keywords: [rest, promise.all, параллельные запросы, server co
## Хорошо
```tsx
-import { petStoreApi } from 'infra/pet-store-api'
+import { petStoreApi, StatusEnum } from 'infra/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 availablePetsPromise = petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Available,
+ })
+ const pendingPetsPromise = petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Pending,
+ })
+ const soldPetsPromise = petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Sold,
+ })
const [availablePets, pendingPets, soldPets] = await Promise.all([
availablePetsPromise,
@@ -45,9 +51,15 @@ export default async function PetsDashboardPage() {
```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' })
+ const availablePets = await petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Available,
+ })
+ const pendingPets = await petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Pending,
+ })
+ const soldPets = await petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Sold,
+ })
return (
}
diff --git a/ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md b/ai/nextjs-style-guide/applied/data-fetch/pass-promise-down.md
similarity index 94%
rename from ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md
rename to ai/nextjs-style-guide/applied/data-fetch/pass-promise-down.md
index d2e5004..0c07820 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/pass-promise-down.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/pass-promise-down.md
@@ -19,13 +19,15 @@ keywords: [rest, promise, suspense, streaming, server components]
```tsx
// src/app/(routes)/pets/page.tsx
import { Suspense } from 'react'
-import { petStoreApi } from 'infra/pet-store-api'
+import { petStoreApi, StatusEnum } from 'infra/pet-store-api'
import { PetListSection } from 'widgets/pet-list-section'
import { PetListSkeleton } from 'widgets/pet-list-section'
import type { Pet } from 'infra/pet-store-api'
export default function PetsPage() {
- const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
+ const petsPromise = petStoreApi.pet.findPetsByStatus({
+ status: StatusEnum.Available,
+ })
return (
diff --git a/ai/nextjs-style-guide/data/rest/strategies/server-await.md b/ai/nextjs-style-guide/applied/data-fetch/server-await.md
similarity index 94%
rename from ai/nextjs-style-guide/data/rest/strategies/server-await.md
rename to ai/nextjs-style-guide/applied/data-fetch/server-await.md
index 87c68b5..53f53e9 100644
--- a/ai/nextjs-style-guide/data/rest/strategies/server-await.md
+++ b/ai/nextjs-style-guide/applied/data-fetch/server-await.md
@@ -29,12 +29,12 @@ SSR/dynamic rendering нужен, когда данные зависят от т
```tsx
// src/app/(routes)/pets/page.tsx
-import { petStoreApi } from 'infra/pet-store-api'
+import { petStoreApi, StatusEnum } from 'infra/pet-store-api'
import { PetsScreen } from 'screens/pets'
export default async function PetsPage() {
const pets = await petStoreApi.pet.findPetsByStatus({
- status: 'available',
+ status: StatusEnum.Available,
})
return
@@ -57,7 +57,7 @@ type PetPageProps = {
export default async function PetPage({ params }: PetPageProps) {
const { id } = await params
- const pet = await petStoreApi.pet.getPetById(Number(id)).catch(() => null)
+ const pet = await petStoreApi.pet.getPetById({ petId: Number(id) }).catch(() => null)
if (!pet) {
notFound()
@@ -73,7 +73,7 @@ export default async function PetPage({ params }: PetPageProps) {
```tsx
// Плохо — хуки нельзя вызывать в Server Component
-const { data } = useGetPetList('available')
+const { data } = useGetPetList({ status: StatusEnum.Available })
// Плохо — прямой fetch в обход клиента
const response = await fetch('https://petstore3.swagger.io/api/v3/pet/findByStatus')
diff --git a/ai/nextjs-style-guide/applied/page-level.md b/ai/nextjs-style-guide/applied/page-level.md
index 5a96eb6..3d41db0 100644
--- a/ai/nextjs-style-guide/applied/page-level.md
+++ b/ai/nextjs-style-guide/applied/page-level.md
@@ -134,7 +134,7 @@ export default async function FeedLayout({ children }: FeedLayoutProps) {
}
```
-Подробнее о стратегиях запросов и начальных данных для клиентских хуков: [REST → Стратегии получения данных](../data/rest/strategies/index.md), [REST → Начальные данные для клиентских хуков](../data/rest/strategies/client-hooks-initial-data.md).
+Подробнее о стратегиях запросов и начальных данных для клиентских хуков: [Получение данных](./data-fetch/index.md), [Начальные данные для клиентских хуков](./data-fetch/client-hooks-initial-data.md).
## Инициализация состояния
diff --git a/ai/nextjs-style-guide/applied/rest-client/index.md b/ai/nextjs-style-guide/applied/rest-client/index.md
new file mode 100644
index 0000000..555fb39
--- /dev/null
+++ b/ai/nextjs-style-guide/applied/rest-client/index.md
@@ -0,0 +1,50 @@
+---
+title: REST-клиент
+description: Настройка REST-клиента сервиса для работы с внешним API.
+keywords: [rest, api, данные, infra, клиент, swr, стратегии]
+---
+
+# REST-клиент
+
+Настройка REST-клиента сервиса для работы с внешним API.
+
+## Настройка
+
+Для каждого внешнего сервиса создаётся отдельный API-клиент: `pet-store-api`, `billing-api`, `maps-api`.
+
+На этом этапе внешний API оформляется как модуль слоя `infra/`.
+
+Клиент отвечает за:
+
+- генерацию или ручное описание методов API;
+- настройку `baseUrl`;
+- заголовки и авторизацию;
+- обработку ошибок;
+- кастомизацию и расширение типов;
+- GET-хуки для клиентских компонентов;
+- прямое использование методов клиента в серверном коде и submit-функциях;
+- публичный API модуля.
+
+Если у API есть OpenAPI-спецификация — клиент генерируется автоматически. Если OpenAPI нет или он неполный — клиент создаётся вручную.
+
+GET-хуки относятся к клиенту, потому что это прозрачные SWR-обёртки над GET-методами этого клиента.
+
+Подробнее:
+
+- [Настройка REST-клиента](./setup/index.md)
+- [Автогенерация из OpenAPI](./setup/auto.md)
+- [Ручное создание](./setup/manual.md)
+- [GET-хуки REST-клиента](./setup/hooks.md)
+- [Использование REST-клиента](./usage.md)
+
+## Как читать раздел
+
+Если API ещё не подключён — начните с [Настройки REST-клиента](./setup/index.md).
+
+Если клиент уже создан и нужно вызвать его методы — откройте [Использование REST-клиента](./usage.md).
+
+Если клиент уже есть, но непонятно как получить данные — начните с раздела [Получение данных](../data-fetch/index.md).
+
+Если данные нужны в Client Component — сначала проверьте, есть ли [GET-хук REST-клиента](./setup/hooks.md).
+
+Если в коде появляется бизнес-смысл вроде `isAuth`, `canEdit`, `hasAccess` — это уже не REST-клиент, а `business/`.
diff --git a/ai/nextjs-style-guide/data/rest/clients/auto.md b/ai/nextjs-style-guide/applied/rest-client/setup/auto.md
similarity index 57%
rename from ai/nextjs-style-guide/data/rest/clients/auto.md
rename to ai/nextjs-style-guide/applied/rest-client/setup/auto.md
index 5098f16..431cd6c 100644
--- a/ai/nextjs-style-guide/data/rest/clients/auto.md
+++ b/ai/nextjs-style-guide/applied/rest-client/setup/auto.md
@@ -1,10 +1,14 @@
---
-title: Автогенерация из OpenAPI
-description: Генерация REST-клиента из OpenAPI-спецификации через @gromlab/api-codegen.
+title: Автогенерация REST-клиента
+description: Генерация REST-клиента из OpenAPI-спецификации.
keywords: [rest, openapi, api-codegen, автогенерация, generated, npx]
---
-# Автогенерация из OpenAPI
+# Автогенерация REST-клиента
+
+Генерация REST-клиента из OpenAPI-спецификации.
+
+## Когда использовать
Автогенерация используется, когда у API есть актуальная OpenAPI-спецификация. Генератор создаёт TypeScript-клиент, типы и методы API, а разработчик вручную добавляет настройку клиента и GET-хуки.
@@ -66,12 +70,39 @@ src/infra/pet-store-api/generated/
Для Petstore нужны GET-операции вида:
```ts
-petStoreApi.pet.findPetsByStatus(...)
-petStoreApi.pet.getPetById(...)
+petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available })
+petStoreApi.pet.getPetById({ petId: 10 })
```
Точные сигнатуры зависят от OpenAPI-схемы и версии генератора. В рабочих задачах всегда сверяйтесь с generated-файлом.
+## Алгоритм для агента
+
+После генерации агент должен действовать по шагам:
+
+1. Открыть `generated/{service-name}.generated.ts`.
+2. Найти фактические имена GET-методов клиента.
+3. Для каждого нужного GET-метода найти generated-тип параметров и тип ответа.
+4. Создать или обновить `client.ts` только для настройки транспорта и экспорта инстанса клиента.
+5. Создать GET-хуки только для реально нужных GET-методов, не для всех методов API на всякий случай.
+6. Для каждого GET-хука создать key-функцию формата `[serviceName, endpoint]`.
+7. В key-функции вернуть `null`, если обязательные параметры не готовы.
+8. В хуке принять `params?: GeneratedParams | null` и `config?: SWRConfiguration`.
+9. В fetcher вызвать generated-метод клиента с `params as GeneratedParams`.
+10. Экспортировать хук и key-функцию из `hooks/index.ts`.
+11. Экспортировать наружу только нужные generated-типы, generated enum, DTO и `hooks` через корневой `index.ts`.
+
+Что агент не должен делать:
+
+- Не использовать ключ `--swr` генератора.
+- Не править `generated/*.generated.ts` руками.
+- Не добавлять GET-хуки для POST, PUT, PATCH, DELETE.
+- Не добавлять бизнес-флаги, тосты, редиректы и UI-состояние в GET-хук.
+- Не создавать словари enum-маппинга внутри GET-хука.
+- Не объявлять DTO и response-типы в файле хука.
+- Не вызывать `useSWR` условно.
+- Не добавлять `throw` в fetcher для неготовых params.
+
## `client.ts`
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
@@ -80,8 +111,14 @@ petStoreApi.pet.getPetById(...)
// src/infra/pet-store-api/client.ts
import { Api, HttpClient } from './generated/pet-store-api.generated'
+const baseUrl = process.env.NEXT_PUBLIC_PET_STORE_API_BASE_URL
+
+if (!baseUrl) {
+ throw new Error('NEXT_PUBLIC_PET_STORE_API_BASE_URL is required')
+}
+
const httpClient = new HttpClient({
- baseUrl: 'https://petstore3.swagger.io/api/v3',
+ baseUrl,
baseApiParams: {
secure: false,
headers: {
@@ -93,10 +130,47 @@ const httpClient = new HttpClient({
export const petStoreApi = new Api(httpClient)
```
-В реальном проекте вместо строки `baseUrl` обычно берётся из env-переменной, нормализуется в `client.ts` и передаётся в `HttpClient` через конфиг.
+Локальное значение `NEXT_PUBLIC_PET_STORE_API_BASE_URL` задаётся в `.env.local`. Не добавляйте fallback вроде `?? 'http://localhost:8080/api/v3'` или `?? ''`: если env-переменная не задана, клиент должен падать с явной ошибкой конфигурации.
`client.ts` не содержит расширения типов, `declare module` и `Extended`-типы. Он только настраивает транспорт и экспортирует инстанс клиента.
+## GET-хуки
+
+GET-хуки пишутся вручную после проверки generated-методов.
+
+Пример для generated-метода `petStoreApi.pet.getPetById({ petId })`:
+
+```ts
+// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
+import type { SWRConfiguration } from 'swr'
+import useSWR from 'swr'
+import { petStoreApi } from '../client'
+import type { GetPetByIdParams, Pet } from '../generated/pet-store-api.generated'
+
+export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
+ if (!params?.petId) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/${params.petId}`] as const
+}
+
+/**
+ * Получает детальную карточку питомца с кешированием результата.
+ */
+export const useGetPetDetail = (
+ params?: GetPetByIdParams | null,
+ config?: SWRConfiguration,
+) => {
+ const key = getPetDetailKey(params)
+ const fetcher = () => petStoreApi.pet.getPetById(params as GetPetByIdParams)
+
+ return useSWR(key, fetcher, config)
+}
+```
+
+Подробный контракт key-функций, `params`, `config` и запретов описан в разделе [GET-хуки REST-клиента](./hooks.md).
+
## Расширение сгенерированных типов
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
@@ -160,7 +234,12 @@ export type { TermRecordItemExtended } from './term'
```ts
// src/infra/pet-store-api/index.ts
export { petStoreApi } from './client'
-export type { Pet } from './generated/pet-store-api.generated'
+export type {
+ FindPetsByStatusParams,
+ GetPetByIdParams,
+ Pet,
+} from './generated/pet-store-api.generated'
+export { PetStatusEnum, StatusEnum } from './generated/pet-store-api.generated'
export * from './hooks'
```
@@ -190,4 +269,4 @@ npm run codegen:pet-store-api
## Следующий шаг
-После генерации и настройки `client.ts` проверьте серверный вызов метода клиента или добавьте [GET-хук REST-клиента](./hooks.md) для Client Components.
+После генерации и настройки `client.ts` проверьте [использование REST-клиента](../usage.md) или добавьте [GET-хук REST-клиента](./hooks.md) для Client Components.
diff --git a/ai/nextjs-style-guide/applied/rest-client/setup/hooks.md b/ai/nextjs-style-guide/applied/rest-client/setup/hooks.md
new file mode 100644
index 0000000..73605a2
--- /dev/null
+++ b/ai/nextjs-style-guide/applied/rest-client/setup/hooks.md
@@ -0,0 +1,313 @@
+---
+title: GET-хуки REST-клиента
+description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
+keywords: [rest, swr, get-хуки, client components, infra]
+---
+
+# GET-хуки REST-клиента
+
+Прозрачные SWR-обёртки над GET-методами REST-клиента.
+
+## Зачем нужны
+
+GET-хуки нужны, чтобы Client Components получали REST-данные через SWR, но не работали с `useSWR`, ключами кеша и fetcher напрямую.
+
+## Где лежат
+
+GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
+
+```text
+src/infra/
+└── pet-store-api/
+ ├── client.ts
+ ├── generated/
+ ├── hooks/
+ │ ├── use-get-pet-list.hook.ts
+ │ ├── use-get-pet-detail.hook.ts
+ │ └── index.ts
+ ├── types/
+ └── index.ts
+```
+
+## Контракт
+
+- Один GET-хук = один GET-метод клиента.
+- Имя GET-хука начинается с `useGet`: `useGetPetList`, `useGetPetDetail`.
+- Имя файла начинается с `use-get`: `use-get-pet-list.hook.ts`.
+- Хук принимает `params?: GeneratedParams | null` и `config?: SWRConfiguration`.
+- Для GET-метода без параметров хук принимает только `config?: SWRConfiguration`.
+- Key-функция принимает те же `params`, что и хук.
+- Key-функция возвращает `null`, если обязательные параметры не готовы.
+- Проверка готовности запроса живёт в key-функции, а не в теле хука.
+- Хук вызывает `useSWR` один раз и безусловно.
+- Fetcher не проверяет `null`, не бросает ошибку и не вызывает метод клиента с `null`.
+- Внутри только SWR-механика: key, fetcher, `useSWR`, `config`.
+- Хук возвращает тип ответа API: generated-тип или DTO из `types/`.
+- Хук не объединяет несколько запросов.
+- Хук не маппит DTO в доменную модель.
+- Хук не вычисляет бизнес-флаги: `isAuth`, `canEdit`, `hasAccess`, `hasPets`.
+- Хук не вызывает тосты, модалки, редиректы и не пишет UI-состояние.
+
+## Формат SWR-ключа
+
+SWR-ключ GET-хука всегда создаётся отдельной экспортируемой функцией.
+
+Формат ключа:
+
+```ts
+['pet-store-api', '/pet/10'] as const
+```
+
+- Первый элемент — имя API-сервиса или REST-клиента в `kebab-case`.
+- Второй элемент — endpoint запроса: path и query string.
+- Key-функция возвращает `null`, когда запрос нельзя выполнять.
+- Key-функция нужна и GET-хуку, и `SWRConfig fallback`.
+- Не используйте произвольные части вроде `['pet-store-api', 'pet', 'detail', params]`.
+- Не используйте только строку endpoint без имени сервиса.
+
+Примеры ключей:
+
+```ts
+export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
+ if (!params?.petId) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/${params.petId}`] as const
+}
+```
+
+```ts
+export const getPetListKey = (params?: FindPetsByStatusParams | null) => {
+ if (!params?.status) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/findByStatus?status=${params.status}`] as const
+}
+```
+
+```ts
+export const getPetListByTagsKey = (params?: FindPetsByTagsParams | null) => {
+ if (!params?.tags.length) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/findByTags?tags=${params.tags.join(',')}`] as const
+}
+```
+
+Если API допускает `0` как валидный идентификатор, не используйте проверку `!params?.id`. В таком случае проверяйте `null` и `undefined` явно.
+
+## Пример списка
+
+```ts
+// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
+import type { SWRConfiguration } from 'swr'
+import useSWR from 'swr'
+import { petStoreApi } from '../client'
+import type {
+ FindPetsByStatusParams,
+ Pet,
+} from '../generated/pet-store-api.generated'
+
+export const getPetListKey = (params?: FindPetsByStatusParams | null) => {
+ if (!params?.status) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/findByStatus?status=${params.status}`] as const
+}
+
+/**
+ * Получает список питомцев по статусу.
+ */
+export const useGetPetList = (
+ params?: FindPetsByStatusParams | null,
+ config?: SWRConfiguration,
+) => {
+ const key = getPetListKey(params)
+ const fetcher = () => petStoreApi.pet.findPetsByStatus(
+ params as FindPetsByStatusParams,
+ )
+
+ return useSWR(key, fetcher, config)
+}
+```
+
+`params as FindPetsByStatusParams` допустим только в fetcher: готовность параметров проверена в key-функции, а при `key = null` SWR не вызывает fetcher.
+
+## Пример detail-запроса
+
+```ts
+// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
+import type { SWRConfiguration } from 'swr'
+import useSWR from 'swr'
+import { petStoreApi } from '../client'
+import type { GetPetByIdParams, Pet } from '../generated/pet-store-api.generated'
+
+export const getPetDetailKey = (params?: GetPetByIdParams | null) => {
+ if (!params?.petId) {
+ return null
+ }
+
+ return ['pet-store-api', `/pet/${params.petId}`] as const
+}
+
+/**
+ * Получает детальную карточку питомца с кешированием результата.
+ */
+export const useGetPetDetail = (
+ params?: GetPetByIdParams | null,
+ config?: SWRConfiguration,
+) => {
+ const key = getPetDetailKey(params)
+ const fetcher = () => petStoreApi.pet.getPetById(params as GetPetByIdParams)
+
+ return useSWR(key, fetcher, config)
+}
+```
+
+## Пример без параметров
+
+```ts
+// src/infra/pet-store-api/hooks/use-get-store-inventory.hook.ts
+import type { SWRConfiguration } from 'swr'
+import useSWR from 'swr'
+import { petStoreApi } from '../client'
+import type { StoreInventory } from '../types'
+
+export const getStoreInventoryKey = () => {
+ return ['pet-store-api', '/store/inventory'] as const
+}
+
+/**
+ * Получает инвентарь магазина.
+ */
+export const useGetStoreInventory = (
+ config?: SWRConfiguration,
+) => {
+ return useSWR(
+ getStoreInventoryKey(),
+ () => petStoreApi.store.getInventory(),
+ config,
+ )
+}
+```
+
+Если generated-метод возвращает безымянный тип вроде `Record`, а тип нужен наружу, вынесите его в `types/`.
+
+## Отложенный запрос
+
+GET-хук может принимать `null` или `undefined` для обязательных параметров. Это означает, что параметры ещё не готовы и запрос выполнять нельзя.
+
+```ts
+const key = getPetDetailKey(params)
+```
+
+Если `params` не готов, key-функция вернёт `null`. SWR не вызовет fetcher для `null`-ключа.
+
+Не добавляйте отдельные `isReady`, `throw new Error(...)` и условный вызов `useSWR`.
+
+## Экспорт
+
+```ts
+// src/infra/pet-store-api/hooks/index.ts
+export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
+export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
+export {
+ getStoreInventoryKey,
+ useGetStoreInventory,
+} from './use-get-store-inventory.hook'
+```
+
+```ts
+// src/infra/pet-store-api/index.ts
+export { petStoreApi } from './client'
+export type {
+ FindPetsByStatusParams,
+ GetPetByIdParams,
+ Pet,
+} from './generated/pet-store-api.generated'
+export { PetStatusEnum, StatusEnum } from './generated/pet-store-api.generated'
+export * from './hooks'
+export type { StoreInventory } from './types'
+```
+
+Наружу импортируют только из `infra/pet-store-api`, не из `generated/` и не из `hooks/` напрямую.
+
+## Где заканчивается infra
+
+```ts
+// Хорошо: infra, прозрачный GET-хук
+const { data: pets } = useGetPetList({ status: StatusEnum.Available })
+```
+
+```ts
+// Хорошо: business, доменная интерпретация
+export const useAvailablePets = () => {
+ const query = useGetPetList({ status: StatusEnum.Available })
+
+ return {
+ ...query,
+ hasPets: Boolean(query.data?.length),
+ }
+}
+```
+
+`hasPets` — не часть GET-запроса, поэтому он не добавляется в `useGetPetList`.
+
+## Что запрещено
+
+```ts
+// Плохо — useSWR в компоненте
+const { data } = useSWR(
+ ['pet-store-api', '/pet/findByStatus?status=available'],
+ () => petStoreApi.pet.findPetsByStatus({ status: StatusEnum.Available }),
+)
+
+// Плохо — проверка готовности размазана по хуку
+export const useGetPetDetail = (params?: GetPetByIdParams | null) => {
+ const key = params?.petId ? getPetDetailKey(params) : null
+ const fetcher = () => {
+ if (!params?.petId) {
+ throw new Error('Pet id is required')
+ }
+
+ return petStoreApi.pet.getPetById(params)
+ }
+
+ return useSWR(key, fetcher)
+}
+
+// Плохо — условный вызов useSWR нарушает rules of hooks
+export const useGetPetDetail = (params?: GetPetByIdParams | null) => {
+ const key = getPetDetailKey(params)
+
+ if (key === null) {
+ return useSWR(null, null)
+ }
+
+ return useSWR(key, () => petStoreApi.pet.getPetById(params))
+}
+
+// Плохо — несколько GET внутри infra-хука
+export const usePetDashboard = () => {
+ const available = useGetPetList({ status: StatusEnum.Available })
+ const sold = useGetPetList({ status: StatusEnum.Sold })
+
+ return { available, sold }
+}
+
+// Плохо — бизнес-флаг внутри GET-хука REST-клиента
+export const useGetPetList = (params?: FindPetsByStatusParams | null) => {
+ const query = useSWR(...)
+
+ return {
+ ...query,
+ hasPets: Boolean(query.data?.length),
+ }
+}
+```
+
+Подробное потребление таких хуков описано в стратегии [Клиентский GET-хук](../../data-fetch/client-get-hook.md).
diff --git a/ai/nextjs-style-guide/data/rest/clients/index.md b/ai/nextjs-style-guide/applied/rest-client/setup/index.md
similarity index 61%
rename from ai/nextjs-style-guide/data/rest/clients/index.md
rename to ai/nextjs-style-guide/applied/rest-client/setup/index.md
index b0e8ff8..30a841a 100644
--- a/ai/nextjs-style-guide/data/rest/clients/index.md
+++ b/ai/nextjs-style-guide/applied/rest-client/setup/index.md
@@ -1,14 +1,18 @@
---
-title: Создание клиента
-description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
+title: Настройка REST-клиента
+description: Подготовка REST-клиента сервиса к использованию.
keywords: [rest, клиент, infra, методы, openapi, get-хуки, swr]
---
-# Создание клиента
+# Настройка REST-клиента
+
+Подготовка REST-клиента сервиса к использованию.
+
+## Что настраиваем
REST-клиент — это infra-модуль, через который проект работает с внешним REST API.
-На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
+На этапе настройки нужно подготовить клиент сервиса: оболочку клиента, методы API и GET-хуки для клиентских компонентов.
## Из чего состоит клиент
@@ -28,6 +32,8 @@ REST-клиент состоит из трёх основных частей:
`client.ts` — только сборочная точка клиента. В нём не размещаются DTO, `declare module`, `Extended`-типы, GET-хуки и бизнес-логика.
+`baseUrl` API задаётся обязательной env-переменной без fallback-значения в коде. Не используйте записи вроде `process.env.NEXT_PUBLIC_PET_STORE_API_BASE_URL ?? 'http://localhost:8080/api/v3'` или `?? ''`: локальный URL должен лежать в `.env.local`, а отсутствие переменной должно приводить к явной ошибке конфигурации.
+
## Методы
Методы описывают конкретные запросы к API.
@@ -50,6 +56,10 @@ REST-клиент состоит из трёх основных частей:
GET-хуки именуются с префиксом `useGet`: `useGetPetList`, `useGetPetDetail`, `useGetCurrentUser`.
+Каждый GET-хук имеет экспортируемую key-функцию. SWR-ключ всегда имеет формат `[serviceName, endpoint]`: например `['pet-store-api', '/pet/10']`.
+
+Хук принимает generated-параметры метода и SWR-настройки: `params?: GetPetByIdParams | null`, `config?: SWRConfiguration`.
+
Подробности:
- [GET-хуки REST-клиента](./hooks.md)
@@ -61,15 +71,18 @@ src/infra/{service-name}/
├── client.ts # самописная оболочка и инстанс клиента
├── generated/ или methods/ # методы API
├── hooks/ # GET-хуки REST-клиента
-├── types/ # DTO, типы API и расширения типов
+├── types/ # DTO, именованные response-типы и расширения типов
├── errors/ # ошибки API, если нужны
└── index.ts # публичный API
```
`index.ts` — единственная точка входа в REST-модуль для внешнего кода.
+Если generated-метод возвращает безымянный тип вроде `Record`, а этот тип нужен снаружи, вынесите его в `types/`. Не объявляйте DTO внутри `hooks/use-get-*.hook.ts`.
+
## Что делаем дальше
1. Создайте методы клиента: [Автогенерация из OpenAPI](./auto.md) или [Ручное создание](./manual.md).
2. Добавьте GET-хуки для GET-запросов: [GET-хуки REST-клиента](./hooks.md).
-3. После создания клиента переходите к [Стратегиям получения данных](../strategies/index.md).
+3. Проверьте прямые вызовы клиента: [Использование REST-клиента](../usage.md).
+4. После настройки клиента переходите к [Получению данных](../../data-fetch/index.md).
diff --git a/ai/nextjs-style-guide/data/rest/clients/manual.md b/ai/nextjs-style-guide/applied/rest-client/setup/manual.md
similarity index 89%
rename from ai/nextjs-style-guide/data/rest/clients/manual.md
rename to ai/nextjs-style-guide/applied/rest-client/setup/manual.md
index cb9820b..a07da4c 100644
--- a/ai/nextjs-style-guide/data/rest/clients/manual.md
+++ b/ai/nextjs-style-guide/applied/rest-client/setup/manual.md
@@ -1,10 +1,14 @@
---
-title: Ручное создание
+title: Ручное создание REST-клиента
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra]
---
-# Ручное создание
+# Ручное создание REST-клиента
+
+Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
+
+## Когда использовать
Ручной REST-клиент используется, когда у API нет OpenAPI-спецификации или она недостаточно точная для автогенерации.
@@ -159,8 +163,14 @@ export function postsMethods(client: PetProjectApiClient) {
import { PetProjectApiClient } from './client'
import { postsMethods } from './methods/posts'
+const baseUrl = process.env.NEXT_PUBLIC_PET_PROJECT_API_BASE_URL
+
+if (!baseUrl) {
+ throw new Error('NEXT_PUBLIC_PET_PROJECT_API_BASE_URL is required')
+}
+
const client = new PetProjectApiClient(
- process.env.NEXT_PUBLIC_API_URL ?? '',
+ baseUrl,
{ 'Content-Type': 'application/json' },
)
@@ -180,8 +190,9 @@ export * from './hooks'
- `fetch` используется только внутри базового клиента.
- DTO запросов и ответов живут в `types/`.
- `client.ts` не содержит DTO, GET-хуки и бизнес-логику.
+- `baseUrl` берётся из обязательной env-переменной без fallback-значения в коде.
- Методы лежат в `methods/` и возвращают DTO.
- GET-хуки добавляются отдельно в `hooks/`, если данные нужны в Client Components.
- Доменные типы и маппинг DTO живут не в REST-клиенте, а в `business/`.
-Следующий шаг: [GET-хуки REST-клиента](./hooks.md) или [Стратегии получения данных](../strategies/index.md).
+Следующий шаг: [Использование REST-клиента](../usage.md), [GET-хуки REST-клиента](./hooks.md) или [Получение данных](../../data-fetch/index.md).
diff --git a/ai/nextjs-style-guide/applied/rest-client/usage.md b/ai/nextjs-style-guide/applied/rest-client/usage.md
new file mode 100644
index 0000000..d4b2097
--- /dev/null
+++ b/ai/nextjs-style-guide/applied/rest-client/usage.md
@@ -0,0 +1,21 @@
+---
+title: Использование REST-клиента
+description: Как вызвать готовый REST-клиент в функции.
+keywords: [rest, api client, submit, generated, pet-store-api]
+---
+
+# Использование REST-клиента
+
+Как вызвать готовый REST-клиент в функции.
+
+## Пример
+
+```ts
+import { petStoreApi } from 'infra/pet-store-api'
+
+export const getPet = async (petId: number) => {
+ const pet = await petStoreApi.pet.getPetById({ petId })
+
+ console.log(pet)
+}
+```
diff --git a/ai/nextjs-style-guide/data/index.md b/ai/nextjs-style-guide/data/index.md
deleted file mode 100644
index 75b1a09..0000000
--- a/ai/nextjs-style-guide/data/index.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-title: Источники данных
-description: Какие источники данных используются в проекте и как с ними работать.
-keywords: [данные, api, rest, realtime, клиент, swr, infra, введение, карта раздела]
----
-
-# Источники данных
-
-Какие источники данных используются в проекте и как с ними работать.
-
-## Принципы раздела
-
-- **Клиент — в `infra/`.** Каждый внешний сервис — отдельный модуль слоя `infra/{service-name}/`.
-- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
-- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
-- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
-
-## Карта раздела
-
-### REST
-
-Канал «запрос-ответ» по HTTP. Покрывает большинство API.
-
-- [REST](./rest/index.md) — обзор раздела: создание клиента и использование.
-- **Создание клиента** — как оформляется REST API в проекте:
- - [Обзор](./rest/clients/index.md) — когда нужен клиент и как выбрать подход.
- - [Автогенерация из OpenAPI](./rest/clients/auto.md) — для API с OpenAPI-спецификацией, через `@gromlab/api-codegen`.
- - [Ручное создание](./rest/clients/manual.md) — для API без схемы, клиент пишется и поддерживается руками.
- - [GET-хуки REST-клиента](./rest/clients/hooks.md) — прозрачные SWR-обёртки над GET-методами клиента.
-- **Использование** — как получать данные через готовый клиент:
- - [Стратегии получения данных](./rest/strategies/index.md) — как выбрать способ получения данных под ситуацию.
- - [Серверный await](./rest/strategies/server-await.md) — прямой `await` метода клиента в Server Components.
- - [Параллельные серверные запросы](./rest/strategies/parallel-server-requests.md) — запуск независимых серверных запросов без waterfall.
- - [Передача промиса ниже](./rest/strategies/pass-promise-down.md) — серверный стриминг через промис и `Suspense`.
- - [Начальные данные для клиентских хуков](./rest/strategies/client-hooks-initial-data.md) — серверный промис в `SWRConfig fallback`.
- - [Клиентский GET-хук](./rest/strategies/client-get-hook.md) — получение данных в Client Components через готовый GET-хук.
- - [Business-композиция](./rest/strategies/business-composition.md) — доменная интерпретация и композиция REST-данных.
-
-### Realtime
-
-Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
-
-- [Realtime](./realtime.md) — клиент realtime в `infra/`, потребление через `useSWRSubscription` или прямые подписки.
-
-## Что даёт раздел
-
-После прочтения раздела понятно:
-
-- Где живёт код работы с API и почему именно там.
-- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
-- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infra/{service-name}/hooks/`.
-- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
-- Как подключать realtime-источники в общую модель работы с данными.
-- Какие правила обязательны и какие отклонения допустимы.
-
-## Что не входит в раздел
-
-- **Глобальное состояние UI** — Stores, формы, фичефлаги. Это [Stores](../applied/stores.md).
-- **Доменная логика** — как данные превращаются в сценарии бизнеса. Это слой `business/` в [Архитектуре](../basics/architecture/index.md).
-- **Хуки общего назначения** — переиспользуемые хуки UI, не привязанные к конкретному API. Отдельный прикладной раздел для них пока не ведётся.
diff --git a/ai/nextjs-style-guide/data/realtime.md b/ai/nextjs-style-guide/data/realtime.md
deleted file mode 100644
index f284c2b..0000000
--- a/ai/nextjs-style-guide/data/realtime.md
+++ /dev/null
@@ -1,79 +0,0 @@
----
-title: Realtime
-description: "Работа с push-данными от сервера: подписки и события."
-keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRSubscription, push, события]
----
-
-# Realtime
-
-Работа с push-данными от сервера: подписки и события.
-
-## Принципы
-
-- **Клиент realtime — в `infra/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
-- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
-- **Использование на клиенте — два сценария:**
- - **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
- - **Прямая подписка** — для побочных эффектов (тосты, нотификации, аналитика), не привязанных к рендеру.
-
-## Размещение клиента
-
-```text
-src/infra/
-└── {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 'infra/notifications'
-
-export function NotificationCounter() {
- const { data: count } = useSWRSubscription(
- ['notifications', 'count'],
- (key, { next }) =>
- subscribe('notifications.count', (value: number) => next(null, value)),
- )
-
- return {count ?? 0}
-}
-```
-
-Плюсы: кеш и дедупликация подписки между несколькими местами рендера; единая модель данных с REST.
-
-## Прямая подписка
-
-Для побочных эффектов, которые не влияют на состояние UI напрямую:
-
-```tsx
-'use client'
-
-import { useEffect } from 'react'
-import { subscribe } from 'infra/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(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infra/`.
-
-Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
diff --git a/ai/nextjs-style-guide/data/rest/clients/hooks.md b/ai/nextjs-style-guide/data/rest/clients/hooks.md
deleted file mode 100644
index 7b4f271..0000000
--- a/ai/nextjs-style-guide/data/rest/clients/hooks.md
+++ /dev/null
@@ -1,206 +0,0 @@
----
-title: GET-хуки REST-клиента
-description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
-keywords: [rest, swr, get-хуки, client components, infra]
----
-
-# GET-хуки REST-клиента
-
-GET-хуки REST-клиента — прозрачные SWR-обёртки над GET-методами API-клиента. Они нужны, чтобы Client Components получали данные с кешированием, дедупликацией и ревалидацией, не работая с `useSWR` напрямую.
-
-## Где лежат
-
-GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
-
-```text
-src/infra/
-└── 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/infra/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(key, fetcher, config)
-}
-```
-
-Функция `getPetListKey` нужна, чтобы один и тот же SWR-ключ использовался внутри GET-хука и при передаче начальных данных через `SWRConfig fallback`.
-
-Пример начальных данных для клиентского хука:
-
-```tsx
-import type { ReactNode } from 'react'
-import { SWRConfig, unstable_serialize } from 'swr'
-import {
- getPetListKey,
- petStoreApi,
-} from 'infra/pet-store-api'
-
-export default function PetsLayout({ children }: { children: ReactNode }) {
- const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
-
- return (
-
- {children}
-
- )
-}
-```
-
-Клиентский компонент при этом ничего не знает про preload/fallback и продолжает вызывать обычный хук:
-
-```tsx
-const { data: pets } = useGetPetList('available')
-```
-
-## Пример detail-запроса
-
-```ts
-// src/infra/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(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/infra/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/infra/pet-store-api/index.ts
-export { petStoreApi } from './client'
-export type { Pet } from './generated/pet-store-api.generated'
-export * from './hooks'
-```
-
-## Где заканчивается infra
-
-```ts
-// Хорошо: infra, прозрачный 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 внутри infra-хука
-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-хук](../strategies/client-get-hook.md).
diff --git a/ai/nextjs-style-guide/data/rest/index.md b/ai/nextjs-style-guide/data/rest/index.md
deleted file mode 100644
index 174a7d5..0000000
--- a/ai/nextjs-style-guide/data/rest/index.md
+++ /dev/null
@@ -1,74 +0,0 @@
----
-title: REST
-description: Как правильно работать с REST API в проекте.
-keywords: [rest, api, данные, infra, клиент, swr, стратегии]
----
-
-# REST
-
-Раздел описывает, как правильно работать с REST API в проекте: создать клиент сервиса и выбрать способ получения данных в приложении.
-
-REST в проекте проходит через два главных этапа:
-
-1. Создание клиента.
-2. Использование.
-
-## 1. Создание клиента
-
-На этом этапе внешний API оформляется как модуль слоя `infra/`.
-
-Клиент отвечает за:
-
-- генерацию или ручное описание методов API;
-- настройку `baseUrl`;
-- заголовки и авторизацию;
-- обработку ошибок;
-- кастомизацию и расширение типов;
-- GET-хуки для клиентских компонентов;
-- публичный API модуля.
-
-Если у API есть OpenAPI-спецификация — клиент генерируется автоматически. Если OpenAPI нет или он неполный — клиент создаётся вручную.
-
-GET-хуки относятся к клиенту, потому что это прозрачные SWR-обёртки над GET-методами этого клиента.
-
-Подробнее:
-
-- [Создание клиента](./clients/index.md)
-- [Автогенерация из OpenAPI](./clients/auto.md)
-- [Ручное создание](./clients/manual.md)
-- [GET-хуки REST-клиента](./clients/hooks.md)
-
-## 2. Использование
-
-После создания клиента нужно определить рендер страницы и выбрать, как получать данные в конкретном месте приложения.
-
-Раздел использования отвечает на вопросы:
-
-- как понять, можно ли сохранить static/ISR;
-- когда страница становится dynamic/SSR;
-- когда получать данные через серверный `await`;
-- когда запускать несколько серверных запросов параллельно;
-- когда передавать промис ниже по дереву;
-- когда передавать начальные данные клиентским GET-хукам;
-- когда использовать GET-хук в клиентском компоненте;
-- когда выносить композицию и бизнес-смысл в `business/`.
-
-Подробнее:
-
-- [Стратегии получения данных](./strategies/index.md)
-- [Серверный await](./strategies/server-await.md)
-- [Параллельные серверные запросы](./strategies/parallel-server-requests.md)
-- [Передача промиса ниже](./strategies/pass-promise-down.md)
-- [Начальные данные для клиентских хуков](./strategies/client-hooks-initial-data.md)
-- [Клиентский GET-хук](./strategies/client-get-hook.md)
-- [Business-композиция](./strategies/business-composition.md)
-
-## Как читать раздел
-
-Если API ещё не подключён — начните с [Создания клиента](./clients/index.md).
-
-Если клиент уже есть, но непонятно как получить данные — начните со [Стратегий получения данных](./strategies/index.md).
-
-Если данные нужны в Client Component — сначала проверьте, есть ли [GET-хук REST-клиента](./clients/hooks.md).
-
-Если в коде появляется бизнес-смысл вроде `isAuth`, `canEdit`, `hasAccess` — это уже не REST-клиент, а `business/`.