docs: добавить раздел «Данные» и реорганизовать документацию
All checks were successful
CI/CD Pipeline / docker (push) Successful in 44s
CI/CD Pipeline / deploy (push) Successful in 6s

- Добавлен раздел «Данные»: REST (автоматическая и ручная генерация клиентов, получение данных в server и client компонентах с инкапсуляцией SWR в хуках), Realtime, введение
- Прикладные разделы переименованы в «Использование», папка перенесена в `docs/docs/usage/`
- Создана группа «Установка и настройка» с папкой `docs/docs/setup/` — туда вынесены PostCSS, Biome, VS Code, алиасы и установка SVG-спрайтов
- Подгруппы «Стили» и «SVG-спрайты» в сайдбаре упразднены — страницы установки и использования разнесены по верхнеуровневым группам
- Удалён устаревший раздел `applied/api.md`
- Перекрёстные ссылки в workflow-разделах и внутри новых страниц синхронизированы с новыми путями
- CONTRIBUTING.md обновлён под новую структуру папок
This commit is contained in:
2026-04-27 00:54:26 +03:00
parent e5e4ace91a
commit 3d93efd90a
31 changed files with 1087 additions and 51 deletions

View File

@@ -30,40 +30,63 @@ const sidebar = [
], ],
}, },
{ {
text: 'Прикладные разделы', text: 'Установка и настройка',
items: [ items: [
{ text: 'Структура проекта', link: '/docs/applied/project-structure' }, { text: 'Алиасы', link: '/docs/setup/aliases' },
{ text: 'Алиасы', link: '/docs/applied/aliases' }, { text: 'Biome', link: '/docs/setup/biome' },
{ text: 'Компоненты', link: '/docs/applied/components' }, { text: 'PostCSS', link: '/docs/setup/postcss' },
{ text: 'Страницы (App Router)', link: '/docs/applied/page-level' }, { text: 'SVG-спрайты', link: '/docs/setup/svg-sprites' },
{ text: 'Шаблоны и генерация кода', link: '/docs/applied/templates-generation' }, { text: 'VS Code', link: '/docs/setup/vscode' },
{
text: 'Стили',
collapsed: true,
items: [
{ text: 'PostCSS', link: '/docs/applied/styles/postcss' },
{ text: 'Использование', link: '/docs/applied/styles/usage' },
],
},
{ text: 'Изображения', link: '/docs/applied/images-sprites' },
{
text: 'SVG-спрайты',
collapsed: true,
items: [
{ text: 'Установка и настройка', link: '/docs/applied/svg-sprites/setup' },
{ text: 'Использование', link: '/docs/applied/svg-sprites/usage' },
],
},
{ text: 'Видео', link: '/docs/applied/video' },
{ text: 'API', link: '/docs/applied/api' },
{ text: 'Stores', link: '/docs/applied/stores' },
{ text: 'Хуки', link: '/docs/applied/hooks' },
{ text: 'Шрифты', link: '/docs/applied/fonts' },
{ text: 'Локализация', link: '/docs/applied/localization' },
{ text: 'Biome', link: '/docs/applied/biome' },
{ text: 'Настройка VS Code', link: '/docs/applied/vscode' },
], ],
}, },
{
text: 'Использование',
items: [
{ text: 'Структура проекта', link: '/docs/usage/project-structure' },
{ text: 'Компоненты', link: '/docs/usage/components' },
{ text: 'Страницы (App Router)', link: '/docs/usage/page-level' },
{ text: 'Шаблоны и генерация кода', link: '/docs/usage/templates-generation' },
{ text: 'Стили', link: '/docs/usage/styles' },
{ text: 'Изображения', link: '/docs/usage/images-sprites' },
{ text: 'SVG-спрайты', link: '/docs/usage/svg-sprites' },
{ text: 'Видео', link: '/docs/usage/video' },
{
text: 'Данные',
collapsed: true,
items: [
{ text: 'Введение', link: '/docs/usage/data/' },
{
text: 'REST',
collapsed: true,
items: [
{
text: 'Клиенты',
collapsed: true,
items: [
{ text: 'Автоматическая генерация', link: '/docs/usage/data/rest/clients/auto' },
{ text: 'Ручная генерация', link: '/docs/usage/data/rest/clients/manual' },
],
},
{
text: 'Получение данных',
collapsed: true,
items: [
{ text: 'Серверные компоненты', link: '/docs/usage/data/rest/fetching/server' },
{ text: 'Клиентские компоненты', link: '/docs/usage/data/rest/fetching/client' },
],
},
],
},
{ text: 'Realtime', link: '/docs/usage/data/realtime' },
],
},
{ text: 'Stores', link: '/docs/usage/stores' },
{ text: 'Хуки', link: '/docs/usage/hooks' },
{ text: 'Шрифты', link: '/docs/usage/fonts' },
{ text: 'Локализация', link: '/docs/usage/localization' },
],
},
]; ];
/** /**

View File

@@ -34,8 +34,13 @@ docs/
│ ├── naming.md │ ├── naming.md
│ ├── documentation.md │ ├── documentation.md
│ └── typing.md │ └── typing.md
── applied/ # Прикладные разделы ── setup/ # Установка: разовая настройка проекта
├── vscode.md ├── aliases.md
│ ├── biome.md
│ ├── postcss.md
│ ├── svg-sprites.md
│ └── vscode.md
└── usage/ # Использование: повседневная работа
├── project-structure.md ├── project-structure.md
├── components.md ├── components.md
├── page-level.md ├── page-level.md
@@ -44,7 +49,7 @@ docs/
├── images-sprites.md ├── images-sprites.md
├── svg-sprites.md ├── svg-sprites.md
├── video.md ├── video.md
├── api.md ├── data/
├── stores.md ├── stores.md
├── hooks.md ├── hooks.md
├── fonts.md ├── fonts.md
@@ -59,7 +64,7 @@ generate-llms.ts # Скрипт генерации llms.txt и R
### Добавление нового раздела ### Добавление нового раздела
1. Создать `.md`-файл в нужной папке (`docs/docs/basics/` или `docs/docs/applied/`). 1. Создать `.md`-файл в нужной папке (`docs/docs/basics/`, `docs/docs/setup/` или `docs/docs/usage/`).
2. Добавить пункт в сайдбар — `.vitepress/config.ts`. 2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
Сайдбар — единственный источник порядка и группировки для `llms.txt`. Сайдбар — единственный источник порядка и группировки для `llms.txt`.
3. Запустить `npm run llms` для обновления `llms.txt` и README. 3. Запустить `npm run llms` для обновления `llms.txt` и README.

View File

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

View File

@@ -7,7 +7,7 @@ keywords: [postcss, postcss.config.mjs, postcss-custom-media, postcss-nesting, a
Установка и настройка CSS-процессора PostCSS в проекте: набор плагинов, конфиг `postcss.config.mjs`. Выполняется один раз при заведении проекта. Установка и настройка CSS-процессора PostCSS в проекте: набор плагинов, конфиг `postcss.config.mjs`. Выполняется один раз при заведении проекта.
Правила написания CSS в компонентах — [Использование](/docs/applied/styles/usage). Правила написания CSS в компонентах — [Использование](/docs/usage/styles).
## Зачем PostCSS ## Зачем PostCSS
@@ -68,4 +68,4 @@ export default {
Опция `importFrom` у `postcss-custom-media` удалена в v10+; её роль теперь выполняет `@csstools/postcss-global-data`. Опция `importFrom` у `postcss-custom-media` удалена в v10+; её роль теперь выполняет `@csstools/postcss-global-data`.
Импортировать `media.css` в файлы компонентов **не нужно** и запрещено правилами (см. [Использование](/docs/applied/styles/usage), раздел «Импорт стилей»). Импортировать `media.css` в файлы компонентов **не нужно** и запрещено правилами (см. [Использование](/docs/usage/styles), раздел «Импорт стилей»).

View File

@@ -7,7 +7,7 @@ keywords: [svg-sprites, установка, настройка, config, паке
Первичная настройка пакета `@gromlab/svg-sprites` в проекте. Выполняется один раз при заведении проекта и при смене мажорной версии пакета. Первичная настройка пакета `@gromlab/svg-sprites` в проекте. Выполняется один раз при заведении проекта и при смене мажорной версии пакета.
Что такое спрайты, как с ними работать и как управлять цветом — [Использование](/docs/applied/svg-sprites/usage). Что такое спрайты, как с ними работать и как управлять цветом — [Использование](/docs/usage/svg-sprites).
## Требования ## Требования
@@ -30,7 +30,7 @@ keywords: [svg-sprites, установка, настройка, config, паке
mkdir -p src/shared/sprites/icons mkdir -p src/shared/sprites/icons
``` ```
Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` посторонних каталогов вне слоёв не заводим. Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](/docs/usage/project-structure), [Архитектура](/docs/basics/architecture/)). В `src/` посторонних каталогов вне слоёв не заводим.
4. Добавить скрипты в `package.json`: 4. Добавить скрипты в `package.json`:

View File

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

View File

@@ -0,0 +1,80 @@
---
title: Realtime
keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRSubscription, push, события]
---
# Realtime
Канал для push-данных: WebSocket, SSE, событийные шины и любой другой источник, инициирующий передачу со стороны сервера. Транспорт не зашит в правила — важна абстракция «подписка».
Получение REST-данных — [REST](/docs/usage/data/rest/clients/auto).
## Принципы
- **Клиент 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

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ title: Использование
# Использование # Использование
Правила написания CSS: PostCSS Modules, форматирование, переменные. Установка и настройка процессора — [PostCSS](/docs/applied/styles/postcss). Правила написания CSS: PostCSS Modules, форматирование, переменные. Установка и настройка процессора — [PostCSS](/docs/setup/postcss).
## Общие правила ## Общие правила

View File

@@ -5,7 +5,7 @@ keywords: [svg, спрайт, sprite, иконка, icon, SvgSprite, превь
# Использование # Использование
Работа с SVG-иконками через сгенерированный компонент `<SvgSprite/>`. Установка пакета — [Установка и настройка](/docs/applied/svg-sprites/setup). Работа с SVG-иконками через сгенерированный компонент `<SvgSprite/>`. Установка пакета — [Установка и настройка](/docs/setup/svg-sprites).
## Шаги ## Шаги

View File

@@ -28,4 +28,4 @@ title: Генерация кода
- Повторяющаяся структура появляется больше одного раза. - Повторяющаяся структура появляется больше одного раза.
- Существующий шаблон не покрывает нужный тип модуля. - Существующий шаблон не покрывает нужный тип модуля.
Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/docs/applied/templates-generation). Инструменты и синтаксис шаблонов — [Шаблоны и генерация кода](/docs/usage/templates-generation).

View File

@@ -12,11 +12,11 @@ title: Добавление UI-модуля
## Порядок действий ## Порядок действий
1. [Сгенерировать](/docs/applied/templates-generation) модуль из соответствующего шаблона в целевой слой. 1. [Сгенерировать](/docs/usage/templates-generation) модуль из соответствующего шаблона в целевой слой.
2. Заполнить модуль логикой и стилями. 2. Заполнить модуль логикой и стилями.
## Дочерние компоненты ## Дочерние компоненты
Если модулю нужны внутренние подкомпоненты — [генерировать](/docs/applied/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя. Если модулю нужны внутренние подкомпоненты — [генерировать](/docs/usage/templates-generation) их из шаблона `component` в папку `ui/` внутри родительского модуля. Дочерние компоненты не экспортируются через `index.ts` родителя.
Правила написания компонентов — [Компоненты](/docs/applied/components). Правила написания компонентов — [Компоненты](/docs/usage/components).

View File

@@ -12,7 +12,7 @@ title: Добавление страницы
## Порядок действий ## Порядок действий
1. [Сгенерировать](/docs/applied/templates-generation) экран из шаблона `screen` в папку `src/screens/`. 1. [Сгенерировать](/docs/usage/templates-generation) экран из шаблона `screen` в папку `src/screens/`.
2. Заполнить экран логикой и стилями. 2. Заполнить экран логикой и стилями.
@@ -20,8 +20,8 @@ title: Добавление страницы
## Правила ## Правила
- Ручное создание файловой структуры экрана запрещено — только [генерация](/docs/applied/templates-generation) из шаблона. - Ручное создание файловой структуры экрана запрещено — только [генерация](/docs/usage/templates-generation) из шаблона.
- Логика, стили и зависимости размещаются в экране, не в `page.tsx`. - Логика, стили и зависимости размещаются в экране, не в `page.tsx`.
- Каждая страница содержит `metadata` с `title` и `description`. - Каждая страница содержит `metadata` с `title` и `description`.
Примеры `page.tsx` и `metadata` — [Page-level компоненты](/docs/applied/page-level). Примеры `page.tsx` и `metadata` — [Page-level компоненты](/docs/usage/page-level).

View File

@@ -19,4 +19,4 @@ title: Начало работы
## Настройка окружения ## Настройка окружения
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/docs/applied/vscode). Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/docs/setup/vscode).

View File

@@ -20,4 +20,4 @@ title: Стилизация
- **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены. - **Магические значения** — произвольные цвета, отступы и скругления запрещены, использовать токены.
- **Глобальные стили** вне `app/styles/` запрещены. - **Глобальные стили** вне `app/styles/` запрещены.
Правила написания CSS, вложенность, медиа-запросы и токены — [Стили: использование](/docs/applied/styles/usage). Правила написания CSS, вложенность, медиа-запросы и токены — [Стили: использование](/docs/usage/styles).