docs: добавить стайлгайд nextjs-style-guide в репозиторий

- Добавлена документация SLM-архитектуры, базовых правил и прикладных разделов
- Добавлены разделы: стили, SVG-спрайты, шаблоны генерации, PostCSS, REST, Realtime
- Удалены устаревшие файлы (спрайты, скрипты, стили из app/)
This commit is contained in:
2026-04-30 19:32:10 +03:00
parent bf792f6159
commit f2358da397
60 changed files with 5308 additions and 372 deletions

View File

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

View File

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

View File

@@ -0,0 +1,201 @@
---
title: Компонент
description: Как создавать React-компоненты внутри SLM-модулей.
---
# Компонент
Как создавать React-компоненты внутри SLM-модулей.
## Назначение
Компонент — минимальная UI-единица проекта. Это один `.tsx` файл без собственной папки, сегментов и публичного API.
Компонент может использовать стили, типы, хуки и другие ресурсы родительского модуля. Наличие связанных файлов в `styles/` или `types/` не превращает компонент в модуль.
## Компонент или модуль
Классификация определяется границей владения:
- `component` — один `.tsx` файл внутри модуля;
- `module` — папка с `index.ts`, сегментами и собственной публичной границей.
```text
user/
├── ui/
│ └── user-avatar.tsx # компонент
├── styles/
│ └── user-avatar.module.css # ресурс родительского модуля
├── types/
│ └── user-avatar.type.ts # ресурс родительского модуля
└── user.tsx # корневой компонент модуля
```
`user-avatar.tsx` остаётся компонентом, потому что у него нет собственной папки, `index.ts` и сегментов.
## Где размещать
Компонент размещается внутри модуля:
- В корне модуля, если это главная UI-сущность модуля.
- В `ui/`, если это вспомогательный компонент модуля.
```text
user/
├── ui/
│ └── user-avatar.tsx
├── styles/
│ ├── user.module.css
│ └── user-avatar.module.css
├── types/
│ ├── user.type.ts
│ └── user-avatar.type.ts
├── user.tsx
└── index.ts
```
`user.tsx` — корневой компонент модуля. `ui/user-avatar.tsx` — вспомогательный компонент этого же модуля.
## Что запрещено
- Заворачивать компонент в папку: `ui/header/header.tsx`.
- Создавать для компонента отдельный `index.ts`.
- Создавать собственные сегменты внутри папки компонента: `ui/header/styles/`, `ui/header/types/`, `ui/header/hooks/` и т.п.
- Декларировать внутри `.tsx` сторы, сервисы, API-клиенты, мапперы или утилиты. Для этого есть сегменты родительского модуля.
- Размещать бизнес-правила прямо в компоненте. Компонент может использовать готовые зависимости модуля, но не определяет их.
- Размещать компонент в `parts/` напрямую. `parts/` содержит только модули.
**Плохо**
```text
user/
└── ui/
└── user-avatar/
├── styles/
│ └── user-avatar.module.css
├── user-avatar.tsx
└── index.ts
```
**Хорошо**
```text
user/
├── ui/
│ └── user-avatar.tsx
├── styles/
│ └── user-avatar.module.css
└── types/
└── user-avatar.type.ts
```
## Стили и типы
Компонент использует ресурсы родительского модуля.
`styles/` и `types/` рядом с корневым компонентом — это сегменты модуля, а не собственные папки `.tsx` файла.
- CSS Module компонента лежит в `styles/` родительского модуля и называется по компоненту: `user-avatar.module.css`.
- Если у компонента есть CSS Module, его корневой класс всегда называется `.root`.
- Типы компонента лежат в `types/` родительского модуля и называются по компоненту: `user-avatar.type.ts`.
- Локальный `type Props` внутри `.tsx` не используется. Типы пропсов всегда выносятся в `types/` родительского модуля.
- Экспорт типа из `types/` не делает его публичным API. Публичным он становится только при реэкспорте из `index.ts` модуля.
Причина `.root`: в DevTools проще находить корневой DOM-узел компонента и отличать его от внутренних элементов.
## Реализация
- Компоненты объявляются через `const`.
- `React.FC` не используется.
- JSDoc-комментарий обязателен для компонента.
- Пропсы деструктурируются в теле компонента.
- `className` объединяется с `styles.root` через `cl()`.
- Побочные эффекты и состояние выносятся в хуки модуля, если перестают быть тривиальными.
- Компонент возвращает JSX и не содержит orchestration-код страницы или бизнес-домена.
`user/types/user-avatar.type.ts`
```ts
import type { ImageProps } from 'next/image'
/**
* Параметры UserAvatar.
*/
export type UserAvatarParams = {}
/** Пропсы базового изображения. */
type RootAttrs = ImageProps
export type UserAvatarProps = RootAttrs & UserAvatarParams
```
`user/ui/user-avatar.tsx`
```tsx
import cl from 'clsx'
import Image from 'next/image'
import type { UserAvatarProps } from '../types/user-avatar.type'
import styles from '../styles/user-avatar.module.css'
/**
* Аватар пользователя.
*
* Используется для:
* - отображения пользователя в карточке
* - отображения пользователя в шапке профиля
*/
export const UserAvatar = (props: UserAvatarProps) => {
const { className, ...imageProps } = props
return <Image {...imageProps} className={cl(styles.root, className)} />
}
```
`user/styles/user-avatar.module.css`
```css
.root {
display: block;
border-radius: 50%;
}
```
## Когда нужен модуль
Решение о выделении модуля остаётся за разработчиком. Поднимать компонент в модуль стоит, если он становится самостоятельной областью:
- получает свои вложенные компоненты;
- получает свои хуки, стор или сервисы;
- получает внутренние мапперы или утилиты;
- требует собственного публичного API;
- начинает переиспользоваться вне родительского модуля;
- становится отдельной зоной параллельной разработки.
Пример: страница — это screen-модуль, а самостоятельные секции страницы — вложенные модули в `parts/`.
```text
screens/home/
├── parts/
│ ├── hero-section/
│ │ ├── styles/
│ │ │ └── hero-section.module.css
│ │ ├── types/
│ │ │ └── hero-section.type.ts
│ │ ├── hero-section.tsx
│ │ └── index.ts
│ └── features-section/
│ ├── styles/
│ │ └── features-section.module.css
│ ├── types/
│ │ └── features-section.type.ts
│ ├── features-section.tsx
│ └── index.ts
├── styles/
│ └── home.module.css
├── types/
│ └── home.type.ts
├── home.screen.tsx
└── index.ts
```
`hero-section` и `features-section` — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа.

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,162 @@
---
title: Модуль
description: Как создавать и организовывать SLM-модули в проекте.
---
# Модуль
Как создавать и организовывать SLM-модули в проекте.
## Назначение
Модуль — основной строительный блок SLM-архитектуры. Это папка с публичным API (`index.ts`) и опциональными сегментами: компонентами, стилями, типами, хуками, сторами, сервисами и вложенными модулями.
Если UI-сущность остаётся одним `.tsx` файлом и использует ресурсы родительского модуля — это [компонент](./component.md), а не модуль. Связанные файлы в `styles/` и `types/` родителя не создают новую модульную границу.
Архитектурное определение: [Модули SLM](../basics/architecture/reference/modules.md). Список сегментов: [Сегменты SLM](../basics/architecture/reference/segments.md).
## Когда нужен модуль
Создавайте модуль, если сущности нужны:
- публичный API;
- хуки, сторы, сервисы, мапперы или утилиты;
- вложенные части;
- переиспользование вне родительского модуля;
- самостоятельная ответственность на слое.
Если понадобилась папка вокруг компонента — это сигнал, что нужен модуль.
## Где размещать
Модуль размещается на самом низком уровне использования.
- Нужен только одному модулю — размещается в `parts/` родителя.
- Нужен одной странице — размещается в `screens/{name}/parts/`.
- Нужен одному layout — размещается в `layouts/{name}/parts/`.
- Переиспользуется между страницами или layout — поднимается в `widgets/`.
- Представляет бизнес-домен — размещается в `business/`.
- Является UI-китом — размещается в `ui/`.
`app/` не содержит модулей. Это слой файлового роутинга и инициализации.
## Структура
Минимальный UI-модуль:
```text
user/
├── user.tsx
└── index.ts
```
Модуль расширяется сегментами только при реальной потребности:
```text
user/
├── ui/
│ └── user-avatar.tsx
├── parts/
│ └── user-posts/
│ ├── user-posts.tsx
│ └── index.ts
├── styles/
│ ├── user.module.css
│ └── user-avatar.module.css
├── types/
│ ├── user.type.ts
│ └── user-avatar.type.ts
├── user.tsx
└── index.ts
```
Корневой компонент опционален. Business- и infrastructure-модули могут состоять только из хуков, сервисов, типов и публичного API.
## `ui/` и `parts/`
`ui/` содержит только компоненты: отдельные `.tsx` файлы без собственных сегментов.
`parts/` содержит только модули: каждая запись внутри `parts/` — папка с собственным `index.ts`. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не кладутся.
```text
user/
├── ui/
│ └── user-avatar.tsx # компонент
└── parts/
└── user-posts/ # модуль
├── styles/
│ └── user-posts.module.css
├── user-posts.tsx
└── index.ts
```
Если компоненту в `ui/` понадобились стили или типы, они добавляются в `styles/` и `types/` родительского модуля. Если компоненту нужны собственные хуки, вложенные части или публичная граница — он переносится в `parts/` как модуль.
## Публичный API
`index.ts` — единственная точка входа в модуль. Внешние импорты внутренних файлов запрещены.
```ts
// user/index.ts
export { User } from './user'
export type { UserProps } from './types/user.type'
```
```ts
// Плохо: импорт в обход публичного API.
import { UserPosts } from 'screens/user/parts/user-posts/user-posts'
// Хорошо: импорт через публичный API родительского модуля.
import { User } from 'screens/user'
```
Вложенный модуль имеет свой `index.ts`, но наружу родителя экспортируется только при необходимости.
## Именование
Базовые правила описаны в разделе [Именование](../basics/naming.md).
- Папка модуля — `kebab-case`: `user-posts/`.
- Файл корневого компонента повторяет имя папки: `user-posts/user-posts.tsx`.
- Корневые модули слоёв наследуют роль слоя в имени файла: `screens/profile/profile.screen.tsx`, `layouts/main/main.layout.tsx`.
- Корневой компонент именуется в `PascalCase`: `UserPosts`.
- Если имя без контекста слишком общее, добавляется префикс родителя или роль слоя: `ProfileUserPosts`, `ProfileScreen`, `MainLayout`.
## Примеры
### Screen-модуль
```text
screens/profile/
├── ui/
│ └── profile-heading.tsx
├── parts/
│ └── activity-feed/
│ ├── styles/
│ │ └── activity-feed.module.css
│ ├── activity-feed.tsx
│ └── index.ts
├── styles/
│ ├── profile.module.css
│ └── profile-heading.module.css
├── types/
│ ├── profile.type.ts
│ └── profile-heading.type.ts
├── profile.screen.tsx
└── index.ts
```
### Business-модуль без корневого компонента
```text
business/auth/
├── hooks/
│ └── use-auth.hook.ts
├── services/
│ └── auth.service.ts
├── stores/
│ └── auth.store.ts
├── types/
│ └── auth.type.ts
└── index.ts
```

View File

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

View File

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

View File

@@ -0,0 +1,101 @@
---
title: Структура проекта
description: Из чего состоит проект и где что лежит.
---
# Структура проекта
Из чего состоит проект и где что лежит.
## Корень репозитория
```text
project-root/
├── .templates/ # Шаблоны для генерации модулей
├── .vscode/ # Настройки и рекомендуемые расширения VS Code
├── public/ # Статика, доступная по прямому URL
├── src/ # Исходный код приложения
├── .env.example # Переменные окружения проекта (шаблон)
├── .env # Переменные окружения проекта (не коммитить)
├── .gitignore
├── AGENTS.md # Инструкции для AI-агентов
├── biome.json # Линтер и форматтер (вместо ESLint + Prettier)
├── next.config.ts # Конфигурация Next.js
├── package.json # Зависимости и скрипты
├── postcss.config.mjs # Конфигурация PostCSS
└── tsconfig.json # Конфигурация TypeScript
```
## Папка `public/`
Хранит статические файлы, которые отдаются по прямому URL без обработки сборщиком:
```text
public/
└── og-image.png
```
Компоненты, стили и другой исходный код здесь не размещаются.
## Папка `src/`
```text
src/
├── app/ # Роутинг Next.js и точка входа приложения
├── layouts/ # Каркасы страниц (header, footer, sidebar)
├── screens/ # Контент конкретной страницы
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
├── business/ # Бизнес-домены (auth, catalog, orders)
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
├── ui/ # UI-кит без бизнес-логики
└── shared/ # Общие ресурсы (утилиты, типы, стили)
```
Принципы организации слоёв описаны в разделе [Архитектура](../basics/architecture/).
### Папка `app/`
Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
`app/` подключает готовую инициализацию из нижних слоёв, но не реализует провайдеры, стили, UI-компоненты, хуки, сторы или сервисы.
Подробнее о границах слоя: [Архитектура → Слои → App](../basics/architecture/reference/layers.md#слой-app).
```text
src/app/
├── layout.tsx # Корневой layout
└── page.tsx # Главная страница
```
## Папка `.templates/`
Содержит шаблоны для генерации кода. Каждый подкаталог — шаблон отдельного типа модуля:
```text
.templates/
├── component/ # Шаблон компонента
├── screen/ # Шаблон экрана
├── layout/ # Шаблон layout
├── widget/ # Шаблон виджета
├── module/ # Шаблон бизнес-модуля
└── store/ # Шаблон стора
```
Подробнее о генерации описано в разделе [Шаблоны генерации](./templates/templates-intro.md).
## Конфигурационные файлы
| Файл | Назначение |
|---|---|
| `next.config.ts` | Настройки Next.js: редиректы, переменные окружения, webpack |
| `tsconfig.json` | Настройки TypeScript: пути, строгость, таргет |
| `biome.json` | Правила линтера и форматтера Biome |
| `postcss.config.mjs` | Подключение PostCSS-плагинов (CSS Modules, custom media) |
| `package.json` | Зависимости, версии, npm-скрипты |
| `AGENTS.md` | Инструкции для AI-агентов, работающих в проекте |
## Переменные окружения
- `.env` — переменные окружения проекта, запрещено коммитить
- `.env.example` — шаблон, коммитится в репозиторий
Переменные с префиксом `NEXT_PUBLIC_` доступны в клиентском коде. Остальные доступны только на сервере.

View File

View File

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

View File

@@ -0,0 +1,271 @@
---
title: Использование стилей
description: Как пишутся стили в проекте.
---
# Использование стилей
Как пишутся стили в проекте.
## Общие правила
- Только **PostCSS** и **CSS Modules** для кастомной стилизации.
- Подход **Mobile First** — стили пишутся от мобильных к десктопу.
- Именование классов — `camelCase` (`.root`, `.buttonNext`, `.itemTitle`).
- Корневой класс каждого CSS Module компонента всегда называется `.root` — это упрощает ориентацию в DevTools и отладку DOM.
- Модификаторы — отдельный класс с `_`, применяется через `&._modifier`.
**Хорошо**
```css
.submitButton {
padding: 8px 16px;
&._disabled {
opacity: 0.5;
}
}
```
**Плохо**
```css
/* Плохо: kebab-case и вложенный элемент вместо отдельного класса. */
.submit-button {
padding: 8px 16px;
&__icon {
margin-right: 8px;
}
}
```
## Вложенность
- Вложенность селекторов запрещена.
- Исключения:
- Псевдоклассы: `&:hover`, `&:active`, `&:focus`, `&:disabled` и т.д.
- Псевдоэлементы: `&::before`, `&::after`.
- Медиа-запросы: `@media`.
- Модификаторы: `&._active`, `&._disabled`.
- Каждый вложенный блок отделяется пустой строкой от предыдущих свойств.
**Хорошо**
```css
.card {
padding: 16px;
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&::after {
content: '';
display: block;
}
&._highlighted {
border-color: var(--color-primary);
}
@media (--md) {
padding: 24px;
}
}
.cardTitle {
font-size: 16px;
@media (--md) {
font-size: 20px;
}
}
```
**Плохо**
```css
/* Плохо: вложенность селекторов, нет пустых строк между блоками. */
.card {
padding: 16px;
.cardTitle {
font-size: 16px;
}
&:hover {
background-color: var(--color-bg-hover);
}
}
```
## Медиа-запросы
- Только **Custom Media Queries**: `@media (--md) {}`.
- Запрещены произвольные breakpoints: `@media (min-width: 768px)`.
- `@media` пишется только **внутри** селектора.
- Запрещено писать `@media` на верхнем уровне с селекторами внутри.
**Хорошо**
```css
.sidebar {
display: none;
@media (--md) {
display: block;
}
}
.sidebarTitle {
font-size: 14px;
@media (--md) {
font-size: 18px;
}
}
```
**Плохо**
```css
/* Плохо: @media на верхнем уровне с селекторами внутри. */
@media (--md) {
.sidebar {
display: block;
}
.sidebarTitle {
font-size: 18px;
}
}
/* Плохо: произвольный breakpoint вместо custom media. */
.sidebar {
@media (min-width: 992px) {
display: block;
}
}
```
## CSS-переменные
- Цвета (`--color-*`), отступы (`--space-*`), скругления (`--radius-*`) определяются в `src/shared/styles/variables.css` через `:root`.
- Файл переменных подключается через `src/shared/styles/global.css`, который импортируется один раз в `src/app/layout.tsx`.
- Не дублировать магические значения в компонентах.
**Хорошо**
```css
/* src/shared/styles/variables.css */
:root {
--color-primary: #3b82f6;
--color-bg: #ffffff;
--color-bg-hover: #f5f5f5;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--radius-1: 4px;
--radius-2: 8px;
}
```
```css
/* компонент */
.card {
padding: var(--space-3);
border-radius: var(--radius-2);
background-color: var(--color-bg);
}
```
**Плохо**
```css
/* Плохо: магические значения вместо переменных. */
.card {
padding: 12px;
border-radius: 8px;
background-color: #ffffff;
}
```
## Custom Media
- Breakpoints определяются через Custom Media Queries в `src/shared/styles/media.css`.
- Custom media подключаются глобально через конфиг PostCSS (плагин `postcss-custom-media`) — не импортировать в файлы стилей.
```css
/* src/shared/styles/media.css */
@custom-media --sm (min-width: 36em);
@custom-media --md (min-width: 62em);
@custom-media --lg (min-width: 82em);
```
## Импорт стилей
- Стили компонента импортируются только внутри своего компонента.
- Запрещено импортировать стили одного компонента в другой.
- Custom media не импортируются в файлы стилей — они подключаются глобально через конфиг PostCSS.
## Форматирование
- Пустая строка между селекторами верхнего уровня.
- Пустая строка перед каждым вложенным блоком (медиа, псевдокласс, модификатор).
**Хорошо**
```css
.userBar {
display: none;
color: var(--color-text);
@media (--md) {
display: flex;
}
}
.userBarButton {
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&._active {
background-color: var(--color-primary);
}
}
```
**Плохо**
```css
/* Плохо: нет пустых строк между селекторами и вложенными блоками. */
.userBar {
display: none;
color: var(--color-text);
@media (--md) {
display: flex;
}
}
.userBarButton {
background-color: var(--color-bg);
&:hover {
background-color: var(--color-bg-hover);
}
&._active {
background-color: var(--color-primary);
}
}
```
## Единицы измерения
- `px` — основная единица измерения.
- Остальные (`em`, `rem`, `%`, `vh`/`vw`) — допускаются по необходимости дизайна.
## Порядок CSS-свойств
В стилях рекомендуется придерживаться логического порядка свойств:
1. Позиционирование (`position`, `top`, `left`, `z-index`).
2. Блочная модель (`display`, `width`, `height`, `margin`, `padding`).
3. Оформление (`background`, `border`, `box-shadow`, `border-radius`).
4. Текст (`font`, `color`, `text-align`, `line-height`).
5. Прочее (`transition`, `animation`, `opacity`, `cursor`).
## Комментарии
- Желательно не писать комментарии в CSS.
- Исключение — нетривиальные хаки и обходные решения, к которым стоит оставить пояснение.

View File

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

View File

@@ -0,0 +1,132 @@
---
title: Настройка SVG-спрайтов
description: Подключение SVG-спрайтов в новом проекте.
keywords: [svg-sprites, установка, настройка, config, пакет, "@gromlab/svg-sprites", svg-sprites.config.ts]
---
# Настройка SVG-спрайтов
Подключение SVG-спрайтов в новом проекте.
## Установка
1. Установить пакет:
```bash
npm install @gromlab/svg-sprites
```
2. Создать `svg-sprites.config.ts` в корне проекта (см. [Стандартный конфиг](#стандартныи-конфиг)).
3. Создать папку входа для SVG-файлов в слое `shared`:
```bash
mkdir -p src/shared/sprites/icons
```
Источники спрайтов живут в `src/shared/sprites/<group>/` — это слой `shared` SLM-архитектуры (см. [Структура проекта](../project-structure.md), [Архитектура](../../basics/architecture/index.md)). В `src/` посторонних каталогов вне слоёв не заводим.
4. Добавить скрипты в `package.json`:
```json
{
"scripts": {
"sprite": "svg-sprites",
"predev": "svg-sprites",
"prebuild": "svg-sprites"
}
}
```
Хуки `predev` и `prebuild` гарантируют, что спрайты и типы всегда актуальны перед запуском и сборкой.
5. Добавить сгенерированные артефакты в `.gitignore`:
```text
# Сгенерированные спрайты и React-компонент
/public/sprites/
/src/ui/svg-sprite/
```
6. Выполнить первую генерацию:
```bash
npm run sprite
```
7. Подключить спрайт в layout. Глобальный спрайт (иконки) подключается через `<link rel="preload">` в корневом layout — браузер загрузит файл заранее и закеширует:
```tsx
// src/app/layout.tsx
import 'shared/styles/global.css'
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'App',
description: '',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ru">
<head>
<link rel="preload" href="/sprites/icons.sprite.stack.svg" as="image" />
</head>
<body>{children}</body>
</html>
)
}
```
Локальные спрайты (если есть) подключаются аналогично в layout конкретной страницы или маршрута.
## Стандартный конфиг
Файл `svg-sprites.config.ts` в корне проекта. Это канон — отклонения только по явной причине.
```ts
// svg-sprites.config.ts
import { defineConfig } from '@gromlab/svg-sprites'
export default defineConfig({
output: 'public/sprites',
publicPath: '/sprites',
react: 'src/ui/svg-sprite',
sprites: [
{ name: 'icons', input: 'src/shared/sprites/icons' },
],
})
```
### Фиксированные значения
| Опция | Значение | Почему так |
|-------|----------|------------|
| `output` | `public/sprites` | Единая папка статики Next.js |
| `publicPath` | `/sprites` | URL-путь без `public/` (Next.js раздаёт `public/` как `/`) |
| `react` | `src/ui/svg-sprite` | Слой `ui/` из архитектуры проекта (→ [Архитектура](../../basics/architecture/index.md)) |
| `sprites[0].name` | `icons` | Основной спрайт всегда называется `icons` |
### Трансформации
Все значения по умолчанию оставлять включёнными:
```ts
transform: {
removeSize: true,
replaceColors: true,
addTransition: true,
}
```
Явно прописывать блок `transform` не нужно — пакет применяет эти значения по умолчанию.
Отключать `replaceColors` — только для отдельного спрайта с фиксированной палитрой (например, брендовые логотипы). Делать это на уровне спрайта, не глобально.
### Режим
По умолчанию `mode: 'stack'` — не указывать явно. Переход на `symbol` требует обоснования: превью и примеры в пакете оптимизированы под `stack`.
## Дальше
- [Использование](./svg-sprites-usage.md) — добавление иконок, компонент `<SvgSprite/>`, управление цветом.

View File

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

View File

@@ -0,0 +1,91 @@
---
title: Создание шаблонов генерации
description: "Структура шаблонов, синтаксис переменных и примеры."
keywords: [шаблоны, templates, .templates, syntax, переменные, kebabCase, pascalCase, scaffold]
---
<!-- @formatter:off -->
::: v-pre
# Создание шаблонов генерации
Структура шаблонов, синтаксис переменных и примеры.
## Структура шаблонов
Все шаблоны лежат в `.templates/` в корне проекта. Каждая папка — отдельный шаблон.
```text
.templates/
├── component/ # шаблон компонента
│ └── {{name.kebabCase}}/
│ ├── styles/
│ │ └── {{name.kebabCase}}.module.css
│ ├── types/
│ │ └── {{name.kebabCase}}.type.ts
│ ├── {{name.kebabCase}}.tsx
│ └── index.ts
└── store/ # шаблон Zustand стора
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.store.ts
├── {{name.kebabCase}}.type.ts
└── index.ts
```
## Синтаксис шаблонов
### Переменные
Переменные работают в именах файлов/папок и внутри файлов:
```text
{{variable}}
```
Переменные могут быть любыми. `name` — дефолтная, подставляется генератором автоматически. Если реализация требует дополнительных параметров — можно использовать произвольные наборы переменных.
### Модификаторы
Модификаторы меняют регистр и формат записи переменной:
```text
{{name.pascalCase}} → MyButton
{{name.camelCase}} → myButton
{{name.kebabCase}} → my-button
{{name.snakeCase}} → my_button
{{name.screamingSnakeCase}} → MY_BUTTON
```
## Как создать новый шаблон
1. Создать папку в `.templates/` с именем шаблона (например `hook`).
2. Внутри разместить файлы и папки, используя `{{name}}` и модификаторы в именах и содержимом.
3. Шаблон сразу доступен и в расширении VS Code, и в CLI.
Пример — создание шаблона для хука:
```text
.templates/
└── hook/
└── {{name.kebabCase}}/
├── {{name.kebabCase}}.hook.ts
└── index.ts
```
```ts
// .templates/hook/{{name.kebabCase}}.hook.ts
export const {{name.camelCase}} = () => {
}
```
```ts
// .templates/hook/index.ts
export { {{name.camelCase}} } from './{{name.kebabCase}}.hook'
```
## Дальше
- [Использование](./templates-usage.md) — генерация через VS Code плагин и CLI.
:::

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
---
title: Использование шаблонов генерации
description: Генерация файлов из шаблонов через VS Code плагин и CLI.
keywords: [шаблоны, templates, generate, VS Code, CLI, gromlab/create, npx, scaffold]
---
# Использование шаблонов генерации
Генерация файлов из шаблонов через VS Code плагин и CLI.
## Через VS Code
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
1. ПКМ на целевой папке в проводнике VS Code.
2. **Generate from template** → выбрать шаблон.
3. Ввести имя (например `button`) — расширение подставит его во все переменные `{{name}}`.
Расширение устанавливается разово на машину разработчика, не через проект.
## Через CLI
[@gromlab/create](https://www.npmjs.com/package/@gromlab/create) — CLI для генерации из тех же шаблонов. Используется через npx, глобальная установка не требуется.
```bash
npx @gromlab/create <шаблон> <имя> [путь]
```
Путь не обязателен — по умолчанию генерация происходит в текущую директорию.
| Команда | Что создаёт |
|---|---|
| `npx @gromlab/create component button` | Компонент в текущей папке |
| `npx @gromlab/create module auth src/business` | Бизнес-модуль |
| `npx @gromlab/create widget header src/widgets` | Виджет |
| `npx @gromlab/create layout admin src/layouts` | Layout |
| `npx @gromlab/create store auth src/business/auth/stores` | Стор |
CLI вызывается через `npx`, в `package.json` отдельно не добавляется.

View File

@@ -0,0 +1,88 @@
---
title: VS Code
description: Единые настройки редактора и расширений для команды.
---
# VS Code
Единые настройки редактора и расширений для команды.
## Структура `.vscode/`
```text
.vscode/
├── extensions.json # Рекомендуемые расширения
└── settings.json # Настройки редактора для проекта
```
Оба файла коммитятся в репозиторий.
## Расширения
Файл `.vscode/extensions.json` определяет список расширений, которые VS Code предложит установить при открытии проекта.
```json
// .vscode/extensions.json
{
"recommendations": [
"biomejs.biome",
"MyTemplateGenerator.mytemplategenerator",
"csstools.postcss"
]
}
```
| Расширение | Назначение |
|---|---|
| [Biome](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) | Линтинг и форматирование кода. Заменяет ESLint и Prettier |
| Template File Generator \| gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) | Генерация файлов и папок из шаблонов `.templates/` через контекстное меню |
| [PostCSS Language Support](https://marketplace.visualstudio.com/items?itemName=csstools.postcss) | Подсветка синтаксиса и автодополнение для PostCSS (`@custom-media`, `@nest` и др.) |
### Зачем это нужно
- Новый участник команды получает все нужные расширения одним кликом.
- Нет разночтений: все используют одинаковый форматтер и линтер.
- Расширения привязаны к проекту, а не к конкретному разработчику.
## Настройки редактора
Файл `.vscode/settings.json` переопределяет пользовательские настройки VS Code на уровне проекта.
```json
// .vscode/settings.json
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"files.associations": {
"*.css": "postcss"
}
}
```
### Разбор настроек
| Настройка | Значение | Что делает |
|---|---|---|
| `editor.defaultFormatter` | `biomejs.biome` | Biome используется как единственный форматтер для всех файлов |
| `editor.formatOnSave` | `true` | Код автоматически форматируется при каждом сохранении |
| `codeActionsOnSave.source.fixAll.biome` | `explicit` | Biome автоматически применяет безопасные исправления при сохранении |
| `codeActionsOnSave.source.organizeImports.biome` | `explicit` | Импорты сортируются и группируются автоматически при сохранении |
| `files.associations` | `"*.css": "postcss"` | Все CSS-файлы открываются с подсветкой PostCSS вместо стандартного CSS |
### Зачем это нужно
- **Единый стиль кода** -- форматирование происходит автоматически, невозможно закоммитить неформатированный код.
- **Автофикс при сохранении** -- распространённые ошибки линтинга исправляются без ручного вмешательства.
- **Сортировка импортов** -- импорты всегда в одном порядке, без конфликтов при мерже.
- **PostCSS-подсветка** -- кастомные at-правила (`@custom-media`, `@define-mixin`) подсвечиваются корректно, а не как ошибки.
## Что не должно быть в `.vscode/`
Не коммитятся файлы, специфичные для конкретного разработчика:
- **Не коммитить**: отладочные конфигурации с локальными путями, персональные сниппеты, настройки тем оформления.
- **Коммитить**: только `extensions.json` и `settings.json` с общими для команды настройками.