Compare commits

..

6 Commits

Author SHA1 Message Date
f645b2ad40 refactor: удалить английскую локаль и упростить структуру
All checks were successful
CI/CD Pipeline / docker (push) Successful in 48s
CI/CD Pipeline / deploy (push) Successful in 7s
- Удалена английская версия документации (docs/en/) и артефакты en
- Контент перенесён docs/ru/ → docs/docs/, URL /ru/ заменён на /docs/
- Из .vitepress/config.ts убраны locales и enSidebar, оставлен один sidebar
- Из лендинга удалён переключатель языка ru/en и en-словарь
- generate-llms.ts переписан без параметра lang; llms.txt, llms-full.txt
  и nextjs-style-guide.zip генерируются в корень docs/public/
- README_RU.md занял место корневого README.md
- Обновлены CONTRIBUTING.md, custom.css, комментарий в Dockerfile
2026-04-26 15:04:10 +03:00
90bf360c06 docs: добавить раздел «Использование» на главных локалей
All checks were successful
CI/CD Pipeline / docker (push) Successful in 58s
CI/CD Pipeline / deploy (push) Successful in 7s
- блок поднят над «Структура документации» — это первое что видит
  читатель после описания
- разделён на две аудитории:
  * «Для AI-агентов»: llms.txt (карта) и llms-full.txt (полный текст)
  * «Для проекта»: ZIP-архив с распаковкой в ./ai/nextjs-style-guide/
- прямые URL на nextjs-style-guide.gromlab.ru
- README.md и README_RU.md (генерируются из docs/{ru,en}/index.md)
  обновлены автоматически
2026-04-25 21:26:45 +03:00
5cf0f0f8ba fix: переключатель языка и иконка репозитория в шапке
- восстановлены link для локалей: ru → /ru/, en → /en/ (были сломаны на /)
- добавлена иконка GitHub в socialLinks шапки VitePress для ru и en
  со ссылкой на https://gromlab.ru/docs/nextjs-style-guide
- root-локаль скрыта из переключателя языка через CSS (display: none
  для href="/"): в дропдауне теперь только Русский и English
- pill-кнопка «Репозиторий» добавлена в блок controls лендинга
  с иконкой GitHub, открывается в новой вкладке
- мобильная вёрстка лендинга переработана: контролы стопкой,
  репозиторий на ≤480px сжимается до иконки 36x36, увеличены
  отступы между блоками (hero / controls / cards) для разделения
2026-04-25 21:14:17 +03:00
464c709859 docs: убрать «воду» из вводных абзацев разделов
All checks were successful
CI/CD Pipeline / docker (push) Successful in 54s
CI/CD Pipeline / deploy (push) Successful in 6s
- удалены обороты «Раздел описывает», «Этот раздел описывает» из
  10 файлов docs/ru
- вводные абзацы переписаны в формате «тема: категории/области»
  без перечисления конкретного содержимого раздела
- удалён frontmatter description из basics/architecture/index.md
  (подтягивается первый абзац после h1 — про SLM Design)
- в CONTRIBUTING.md добавлен раздел «Вводный абзац» с правилами
  и блоками «Хорошо/Плохо»: что делать, чего избегать, проверка
  на излишнюю конкретику
2026-04-25 20:15:10 +03:00
64db18917b fix: синхронизировать EN-лендинг с русским
All checks were successful
CI/CD Pipeline / docker (push) Successful in 54s
CI/CD Pipeline / deploy (push) Successful in 6s
- обновлён EN tagline на лендинге под новый стиль (RU уже был обновлён)
- английская карточка «For Assistant» приведена к структуре русской:
  две кнопки llms.txt и llms-full.txt с заглушечными ссылками (#)
  и бейджем «in development»
- шаблон карточки теперь поддерживает badge при наличии buttons:
  карточка приглушается, кнопки не кликаются
2026-04-25 20:00:52 +03:00
ae103e962e feat: llms-full.txt, README архива и доработка лендинга
All checks were successful
CI/CD Pipeline / docker (push) Successful in 46s
CI/CD Pipeline / deploy (push) Successful in 8s
- добавлен генератор llms-full.txt: вся документация локали в одном
  файле с мета-якорями, порядок повторяет sidebar
- архив теперь содержит README.md как точку входа: карта документации
  с относительными ссылками, описаниями и метаинфо сборки
- ссылки /ru/... в .md-файлах архива преобразуются в относительные
  пути (через path.relative) — внутренняя навигация работает локально
- веб-index.md удаляется из архива (его роль выполняет README.md)
- llms-full.txt добавлен в архив для одноразового чтения LLM
- в sidebar добавлен пункт «Главная» / «Home» со ссылкой на корень локали
- карточка «Ассистенту» на лендинге: две кнопки llms.txt и llms-full.txt
  с открытием в новой вкладке
- активирована карточка «Скачать правила» (ru) с ссылкой на zip-архив
- удалён устаревший блок «Для ассистентов» из docs/{ru,en}/index.md
- обновлены описания на главных локалей и заменён FSD на SLM в EN
- в манифесте появилось поле llmsFull рядом с llms
2026-04-25 19:56:44 +03:00
70 changed files with 602 additions and 770 deletions

View File

@@ -1,93 +1,51 @@
import { defineConfig } from 'vitepress'; import { defineConfig } from 'vitepress';
const ruSidebar = [ const sidebar = [
{
text: 'Главная',
link: '/docs/',
},
{ {
text: 'Workflow', text: 'Workflow',
link: '/ru/workflow', link: '/docs/workflow',
}, },
{ {
text: 'Базовые правила', text: 'Базовые правила',
items: [ items: [
{ text: 'Технологии и библиотеки', link: '/ru/basics/tech-stack' }, { text: 'Технологии и библиотеки', link: '/docs/basics/tech-stack' },
{ text: 'Именование', link: '/ru/basics/naming' }, { text: 'Именование', link: '/docs/basics/naming' },
{ {
text: 'Архитектура', text: 'Архитектура',
collapsed: true, collapsed: true,
items: [ items: [
{ text: 'Обзор', link: '/ru/basics/architecture/' }, { text: 'Обзор', link: '/docs/basics/architecture/' },
{ text: 'Слои', link: '/ru/basics/architecture/reference/layers' }, { text: 'Слои', link: '/docs/basics/architecture/reference/layers' },
{ text: 'Модули', link: '/ru/basics/architecture/reference/modules' }, { text: 'Модули', link: '/docs/basics/architecture/reference/modules' },
{ text: 'Сегменты', link: '/ru/basics/architecture/reference/segments' }, { text: 'Сегменты', link: '/docs/basics/architecture/reference/segments' },
], ],
}, },
{ text: 'Стиль кода', link: '/ru/basics/code-style' }, { text: 'Стиль кода', link: '/docs/basics/code-style' },
{ text: 'Документирование', link: '/ru/basics/documentation' }, { text: 'Документирование', link: '/docs/basics/documentation' },
{ text: 'Типизация', link: '/ru/basics/typing' }, { text: 'Типизация', link: '/docs/basics/typing' },
], ],
}, },
{ {
text: 'Прикладные разделы', text: 'Прикладные разделы',
items: [ items: [
{ text: 'Структура проекта', link: '/ru/applied/project-structure' }, { text: 'Структура проекта', link: '/docs/applied/project-structure' },
{ text: 'Компоненты', link: '/ru/applied/components' }, { text: 'Компоненты', link: '/docs/applied/components' },
{ text: 'Страницы (App Router)', link: '/ru/applied/page-level' }, { text: 'Страницы (App Router)', link: '/docs/applied/page-level' },
{ text: 'Шаблоны и генерация кода', link: '/ru/applied/templates-generation' }, { text: 'Шаблоны и генерация кода', link: '/docs/applied/templates-generation' },
{ text: 'Стили', link: '/ru/applied/styles' }, { text: 'Стили', link: '/docs/applied/styles' },
{ text: 'Изображения', link: '/ru/applied/images-sprites' }, { text: 'Изображения', link: '/docs/applied/images-sprites' },
{ text: 'SVG-спрайты', link: '/ru/applied/svg-sprites' }, { text: 'SVG-спрайты', link: '/docs/applied/svg-sprites' },
{ text: 'Видео', link: '/ru/applied/video' }, { text: 'Видео', link: '/docs/applied/video' },
{ text: 'API', link: '/ru/applied/api' }, { text: 'API', link: '/docs/applied/api' },
{ text: 'Stores', link: '/ru/applied/stores' }, { text: 'Stores', link: '/docs/applied/stores' },
{ text: 'Хуки', link: '/ru/applied/hooks' }, { text: 'Хуки', link: '/docs/applied/hooks' },
{ text: 'Шрифты', link: '/ru/applied/fonts' }, { text: 'Шрифты', link: '/docs/applied/fonts' },
{ text: 'Локализация', link: '/ru/applied/localization' }, { text: 'Локализация', link: '/docs/applied/localization' },
{ text: 'Настройка VS Code', link: '/ru/applied/vscode' }, { text: 'Настройка VS Code', link: '/docs/applied/vscode' },
],
},
];
const enSidebar = [
{
text: 'Processes',
items: [
{ text: 'Getting Started', link: '/en/workflow/getting-started' },
{ text: 'Creating an App', link: '/en/workflow/creating-app' },
{ text: 'Creating Pages', link: '/en/workflow/creating-pages' },
{ text: 'Creating Components', link: '/en/workflow/creating-components' },
{ text: 'Styling', link: '/en/workflow/styling' },
{ text: 'Data Fetching', link: '/en/workflow/data-fetching' },
{ text: 'State Management', link: '/en/workflow/state-management' },
{ text: 'Localization', link: '/en/workflow/localization' },
],
},
{
text: 'Basic Rules',
items: [
{ text: 'Tech Stack', link: '/en/basics/tech-stack' },
{ text: 'Architecture', link: '/en/basics/architecture' },
{ text: 'Code Style', link: '/en/basics/code-style' },
{ text: 'Naming', link: '/en/basics/naming' },
{ text: 'Documentation', link: '/en/basics/documentation' },
{ text: 'Typing', link: '/en/basics/typing' },
],
},
{
text: 'Applied Sections',
items: [
{ text: 'VS Code Setup', link: '/en/applied/vscode' },
{ text: 'Project Structure', link: '/en/applied/project-structure' },
{ text: 'Components', link: '/en/applied/components' },
{ text: 'Page-level Components', link: '/en/applied/page-level' },
{ text: 'Templates & Code Generation', link: '/en/applied/templates-generation' },
{ text: 'Styles', link: '/en/applied/styles' },
{ text: 'Images', link: '/en/applied/images-sprites' },
{ text: 'SVG Sprites', link: '/en/applied/svg-sprites' },
{ text: 'Video', link: '/en/applied/video' },
{ text: 'API', link: '/en/applied/api' },
{ text: 'Stores', link: '/en/applied/stores' },
{ text: 'Hooks', link: '/en/applied/hooks' },
{ text: 'Fonts', link: '/en/applied/fonts' },
{ text: 'Localization', link: '/en/applied/localization' },
], ],
}, },
]; ];
@@ -115,8 +73,9 @@ export default defineConfig({
// (попадают в корень `dist/` как статика). Исключаем из сканирования // (попадают в корень `dist/` как статика). Исключаем из сканирования
// страниц, иначе VitePress рендерит их как HTML-страницы. // страниц, иначе VitePress рендерит их как HTML-страницы.
srcExclude: ['public/**'], srcExclude: ['public/**'],
lang: 'ru-RU',
title: 'NextJS Style Guide', title: 'NextJS Style Guide',
description: 'Правила и стандарты разработки на NextJS и TypeScript', description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
vite: { vite: {
plugins: [utf8TextPlugin], plugins: [utf8TextPlugin],
@@ -125,38 +84,18 @@ export default defineConfig({
}, },
}, },
locales: {
root: {
label: 'Languages',
lang: 'en',
},
ru: {
label: 'Русский',
lang: 'ru-RU',
link: '/ru/',
description: 'Стандарты разработки на Next.js + TypeScript с архитектурой SLM',
themeConfig: { themeConfig: {
sidebar: ruSidebar, sidebar,
socialLinks: [
{ icon: 'github', link: 'https://gromlab.ru/docs/nextjs-style-guide' },
],
}, },
// Расширенный блок описания для llms.txt — даёт LLM полный // Расширенный блок описания для llms.txt — даёт LLM полный
// технический контекст: стек, методология, охват тем. // технический контекст: стек, методология, охват тем.
// Используется в generate-llms.ts.
llmsBlockquote: llmsBlockquote:
'Стандарты разработки frontend-приложений на Next.js (App Router) + TypeScript + React с архитектурой SLM (Scoped Layered Module Design — модульная архитектура со слоями ответственности, где каждый модуль содержит всё необходимое: компоненты, хуки, сторы, типы, стили).', 'Стандарты разработки frontend-приложений на Next.js (App Router) + TypeScript + React с архитектурой SLM (Scoped Layered Module Design — модульная архитектура со слоями ответственности, где каждый модуль содержит всё необходимое: компоненты, хуки, сторы, типы, стили).',
llmsContext: llmsContext:
'Стек: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nДокументация покрывает архитектуру SLM (слои, модули, сегменты, направление зависимостей, публичный API), правила оформления кода (именование, форматирование, импорты, типизация, JSDoc), реализацию компонентов и хуков, работу с App Router, кодогенерацию из шаблонов, стилизацию (Mobile First, токены), работу с API и сокетами, управление состоянием через Zustand, локализацию, ассеты (шрифты, изображения, SVG-спрайты) и настройку VS Code.', 'Стек: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nДокументация покрывает архитектуру SLM (слои, модули, сегменты, направление зависимостей, публичный API), правила оформления кода (именование, форматирование, импорты, типизация, JSDoc), реализацию компонентов и хуков, работу с App Router, кодогенерацию из шаблонов, стилизацию (Mobile First, токены), работу с API и сокетами, управление состоянием через Zustand, локализацию, ассеты (шрифты, изображения, SVG-спрайты) и настройку VS Code.',
}, } as any);
en: {
label: 'English',
lang: 'en-US',
link: '/en/',
description: 'Next.js + TypeScript development standards with SLM architecture',
themeConfig: {
sidebar: enSidebar,
},
llmsBlockquote:
'Frontend development standards for Next.js (App Router) + TypeScript + React projects with SLM architecture (Scoped Layered Module Design — a modular architecture with responsibility layers, where each module contains everything it needs: components, hooks, stores, types, styles).',
llmsContext:
'Stack: React, TypeScript, Next.js App Router, Mantine UI, SWR, Zustand, i18next, PostCSS Modules, Vitest, clsx.\n\nThe documentation covers SLM architecture (layers, modules, segments, dependency direction, public API), code conventions (naming, formatting, imports, typing, JSDoc), component and hook implementation, App Router usage, code generation from templates, styling (Mobile First, design tokens), API and socket integration, state management via Zustand, localization, assets (fonts, images, SVG sprites), and VS Code setup.',
},
},
});

View File

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

View File

@@ -7,9 +7,8 @@
Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript. Документационный сайт с правилами и стандартами фронтенд-разработки на Next.js + TypeScript.
- Движок: VitePress - Движок: VitePress
- Языки: русский (основной), английский - Язык: русский
- Русская версия: `docs/ru/` - Контент: `docs/docs/`
- Английская версия: `docs/en/`
## Команды ## Команды
@@ -17,51 +16,53 @@
|---------|-----------| |---------|-----------|
| `npm run dev` | Локальный сервер разработки | | `npm run dev` | Локальный сервер разработки |
| `npm run build` | Сборка статического сайта | | `npm run build` | Сборка статического сайта |
| `npm run llms` | Генерация `generated/{lang}/llms.txt` (карта документации для LLM) и README | | `npm run llms` | Генерация `llms.txt` (карта документации для LLM) и README |
## Структура файлов ## Структура файлов
``` ```
docs/ docs/
├── ru/ # Русская версия (основная) ├── index.md # Лендинг (URL `/`)
│ ├── index.md # Главная страница └── docs/ # Контент документации (URL `/docs/...`)
├── basics/ # Базовые правила ├── index.md # Главная страница
│ │ ├── tech-stack.md ├── workflow.md
│ │ ├── architecture.md ├── workflow/ # Процессы разработки
│ │ ├── code-style.md ├── basics/ # Базовые правила
│ ├── naming.md │ ├── tech-stack.md
│ ├── documentation.md │ ├── architecture/
── typing.md ── code-style.md
── applied/ # Прикладные разделы ── naming.md
├── vscode.md ├── documentation.md
── project-structure.md ── typing.md
── components.md ── applied/ # Прикладные разделы
├── page-level.md ├── vscode.md
├── templates-generation.md ├── project-structure.md
├── styles.md ├── components.md
├── images-sprites.md ├── page-level.md
├── svg-sprites.md ├── templates-generation.md
├── video.md ├── styles.md
├── api.md ├── images-sprites.md
├── stores.md ├── svg-sprites.md
├── hooks.md ├── video.md
├── fonts.md ├── api.md
── localization.md ── stores.md
├── en/ # Английская версия (зеркало ru/) ├── hooks.md
├── fonts.md
└── localization.md
.vitepress/ .vitepress/
── config.ts # Конфигурация VitePress, сайдбары, локали ── config.ts # Конфигурация VitePress, сайдбар
generated/
├── ru/llms.txt # Карта документации для LLM (ru, llmstxt.org)
└── en/llms.txt # Карта документации для LLM (en, llmstxt.org)
generate-llms.ts # Скрипт генерации llms.txt и README generate-llms.ts # Скрипт генерации llms.txt и README
``` ```
Сгенерированные артефакты (`docs/public/`): `llms.txt`, `llms-full.txt`,
`nextjs-style-guide.zip`, `manifest.json`, копии `.md` в `docs/public/docs/`.
### Добавление нового раздела ### Добавление нового раздела
1. Создать `.md`-файл в нужной папке (`basics/` или `applied/`). 1. Создать `.md`-файл в нужной папке (`docs/docs/basics/` или `docs/docs/applied/`).
2. Добавить пункт в сайдбар — `.vitepress/config.ts` (оба языка, если есть перевод). 2. Добавить пункт в сайдбар — `.vitepress/config.ts`.
Сайдбар — единственный источник порядка и группировки для `llms.txt`. Сайдбар — единственный источник порядка и группировки для `llms.txt`.
3. Запустить `npm run llms` для обновления `generated/{lang}/llms.txt`. 3. Запустить `npm run llms` для обновления `llms.txt` и README.
## Два типа документации ## Два типа документации
@@ -185,6 +186,50 @@ title: Название раздела
- Подсекции внутри `h2``h3`. - Подсекции внутри `h2``h3`.
- `h4` не используется. - `h4` не используется.
### Вводный абзац
Абзац сразу после `h1` отвечает на вопрос «о чём этот раздел?».
Он попадает в `llms.txt` и `README.md` архива как краткое описание,
поэтому должен быть плотным и без воды.
**Правила:**
- Не начинать с «Раздел описывает», «Этот раздел», «В этом разделе»,
«Здесь рассмотрено», «В этом документе».
- Начинать с подлежащего — самой темы (`Слои SLM:`, `Соглашения об именовании:`).
- Двоеточие или тире для перечисления **категорий и областей**, а не
конкретных значений из содержимого.
- Не дублировать содержимое: если внутри раздела 12 правил —
не перечислять их во вводном абзаце.
- Не аргументировать («единые правила делают код предсказуемым»).
- 12 предложения.
**Проверка:** если при добавлении нового правила/инструмента/раздела
вводный абзац придётся править — он слишком конкретный.
**Хорошо:**
```markdown
Слои SLM: назначение, классификация, направление зависимостей, правила.
```
```markdown
Базовый стек проекта по областям: UI, архитектура, данные, состояние,
локализация, тестирование, стили, генерация кода.
```
**Плохо:**
```markdown
Раздел описывает слои SLM: что такое слой, какие бывают, как между
ними направлены зависимости и какие правила действуют на каждом.
```
```markdown
Этот раздел описывает базовый стек технологий и библиотек, принятый в
проекте. React, TypeScript, Next.js, SWR, Zustand, i18next.
```
### Примеры кода ### Примеры кода
- Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `. - Блоки кода с указанием языка: ` ```tsx `, ` ```css `, ` ```bash `, ` ```text `.

View File

@@ -1,6 +1,6 @@
FROM node:24-alpine AS build FROM node:24-alpine AS build
WORKDIR /app WORKDIR /app
# zip нужен для упаковки nextjs-style-guide-{lang}.zip # zip нужен для упаковки nextjs-style-guide.zip
RUN apk add --no-cache zip RUN apk add --no-cache zip
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm ci

View File

@@ -1,58 +1,69 @@
# NextJS Style Guide # NextJS Style Guide
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure. Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
## Documentation Structure ## Использование
### Processes **Для AI-агентов:**
**What to do** in a specific situation — step-by-step instructions. - [Карта разделов](https://nextjs-style-guide.gromlab.ru/llms.txt) — `llms.txt`, оглавление со ссылками на разделы.
- [Полный текст](https://nextjs-style-guide.gromlab.ru/llms-full.txt) — `llms-full.txt`, вся документация одним файлом.
| Section | Answers the question | **Для проекта:**
|---------|---------------------|
| Getting Started | What tools to install before starting development? |
| Creating an App | How to create a new project, where to get a template? |
| Creating Pages | How to add a page: routing and screen? |
| Creating Components | How to generate components using templates? |
| Styling | What to use: Mantine, tokens, or PostCSS? |
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
| State Management | When and how to create a store (Zustand)? |
| Localization | How to add translations and work with i18next? |
### Basic Rules - [Архив с правилами](https://nextjs-style-guide.gromlab.ru/nextjs-style-guide.zip) — `nextjs-style-guide.zip`, набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
**What the code should look like** — standards not tied to a specific technology. ## Структура документации
| Section | Answers the question | ### Workflow
|---------|---------------------|
| Tech Stack | What stack do we use? |
| Architecture | How are FSD layers, dependencies, and public API structured? |
| Code Style | How to format code: indentation, quotes, imports, early return? |
| Naming | How to name files, variables, components, hooks? |
| Documentation | How to write JSDoc: what to document and what not? |
| Typing | How to type: type vs interface, any/unknown? |
### Applied Sections **Что делать и в каком порядке** — пошаговые инструкции.
**How a specific area works** — rules, structure, and code examples for specific technologies and tools. | Раздел | Отвечает на вопрос |
|--------|-------------------|
| Начало работы | Что нужно знать перед началом разработки? |
| Создание проекта | Как начать новый проект? |
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
| Добавление страницы | Как добавить новую страницу в проект? |
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
| Стилизация | Как стилизовать компоненты в проекте? |
| Получение данных | Как получать данные с сервера? |
| Управление состоянием | Как работать с состоянием? |
| Локализация | Как добавлять переводы и подключать локализацию? |
### Базовые правила
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Технологии и библиотеки | Какой стек используем? |
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
| Именование | Как называть файлы, переменные, компоненты, хуки? |
| Документирование | Как писать JSDoc: что документировать, а что нет? |
| Типизация | Как типизировать: type vs interface, any/unknown? |
### Прикладные разделы
**Как это настроить и использовать** — конфигурация, структура и примеры кода для конкретных областей.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Настройка VS Code | Как настроить редактор для проекта? |
| Структура проекта | Как организованы папки и файлы по SLM? |
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
| Изображения | _(не заполнен)_ |
| SVG-спрайты | _(не заполнен)_ |
| Видео | _(не заполнен)_ |
| API | _(не заполнен)_ |
| Stores | _(не заполнен)_ |
| Хуки | _(не заполнен)_ |
| Шрифты | _(не заполнен)_ |
| Локализация | _(не заполнен)_ |
| Section | Answers the question |
|---------|---------------------|
| Project Structure | How are folders and files organized by FSD? |
| Components | How is a component structured: files, props, clsx? |
| Page-level Components | How to define layout, page, loading, error, not-found? |
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
| Images | _(not filled)_ |
| SVG Sprites | _(not filled)_ |
| Video | _(not filled)_ |
| API | _(not filled)_ |
| Stores | _(not filled)_ |
| Hooks | _(not filled)_ |
| Fonts | _(not filled)_ |
| Localization | _(not filled)_ |
## For Assistants
Documentation map with links to all sections ([llmstxt.org](https://llmstxt.org) format):
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/en/llms.txt

View File

@@ -1,61 +0,0 @@
# NextJS Style Guide
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы.
## Для ассистентов
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)):
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
## Структура документации
### Workflow
**Что делать и в каком порядке** — пошаговые инструкции.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Начало работы | Что нужно знать перед началом разработки? |
| Создание проекта | Как начать новый проект? |
| Генерация кода | Какие модули должны генерироваться из шаблонов? |
| Добавление страницы | Как добавить новую страницу в проект? |
| Добавление UI-модуля | Как создать компонент, бизнес-модуль, виджет или layout? |
| Стилизация | Как стилизовать компоненты в проекте? |
| Получение данных | Как получать данные с сервера? |
| Управление состоянием | Как работать с состоянием? |
| Локализация | Как добавлять переводы и подключать локализацию? |
### Базовые правила
**Каким должен быть код** — стандарты, не привязанные к конкретной технологии.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Технологии и библиотеки | Какой стек используем? |
| Архитектура | Как устроены слои SLM, зависимости, публичный API? |
| Стиль кода | Как оформлять код: отступы, кавычки, импорты, early return? |
| Именование | Как называть файлы, переменные, компоненты, хуки? |
| Документирование | Как писать JSDoc: что документировать, а что нет? |
| Типизация | Как типизировать: type vs interface, any/unknown? |
### Прикладные разделы
**Как это настроить и использовать** — конфигурация, структура и примеры кода для конкретных областей.
| Раздел | Отвечает на вопрос |
|--------|-------------------|
| Настройка VS Code | Как настроить редактор для проекта? |
| Структура проекта | Как организованы папки и файлы по SLM? |
| Компоненты | Как устроен компонент: файлы, пропсы, clsx? |
| Page-level компоненты | Как описывать layout, page, loading, error, not-found? |
| Шаблоны и генерация кода | Как работают шаблоны, синтаксис и инструменты генерации? |
| Стили | Как писать CSS: PostCSS Modules, вложенность, медиа, токены? |
| Изображения | _(не заполнен)_ |
| SVG-спрайты | _(не заполнен)_ |
| Видео | _(не заполнен)_ |
| API | _(не заполнен)_ |
| Stores | _(не заполнен)_ |
| Хуки | _(не заполнен)_ |
| Шрифты | _(не заполнен)_ |
| Локализация | _(не заполнен)_ |

View File

@@ -6,7 +6,7 @@ title: Компоненты
Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`. Правила написания React-компонентов: файловая структура модуля, типизация пропсов, документирование и реализация. Раздел охватывает компоненты всех слоёв — от `shared/ui` до `screens`.
Архитектурные слои и их назначение описаны в разделе [Архитектура](/ru/basics/architecture/). Архитектурные слои и их назначение описаны в разделе [Архитектура](/docs/basics/architecture/).
## Правила организации ## Правила организации
@@ -43,7 +43,7 @@ container/
- **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно. - **`type` вместо `interface`** — гибче для пропсов: поддерживает union, intersection, mapped types. Declaration merging пропсам не нужно.
- **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра. - **Без `FC`** — неявно добавляет `children`, усложняет дженерики, не даёт преимуществ перед аннотацией параметра.
- **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных. - **Типы в `types/`, а не в `.tsx`** — предотвращает циклические зависимости (компонент импортирует хук, хук импортирует тип из компонента) и разделяет ответственность: `.tsx` для рендера, `.type.ts` для данных.
- **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/ru/basics/typing). - **Без возвращаемого типа** — TypeScript выводит из JSX. Осознанное исключение из [базового правила](/docs/basics/typing).
## Реализация ## Реализация

View File

@@ -4,7 +4,7 @@ title: Структура проекта
# Структура проекта # Структура проекта
Раздел описывает расположение файлов и папок в проекте Next.js (App Router). Файловая организация Next.js-проекта по архитектуре SLM.
## Корень репозитория ## Корень репозитория

View File

@@ -4,7 +4,7 @@ title: Стили
# Стили # Стили
Раздел описывает правила написания CSS: PostCSS Modules, вложенность, медиа-запросы, переменные, форматирование. Правила написания CSS: PostCSS Modules, форматирование, переменные.
## Общие правила ## Общие правила

View File

@@ -1,6 +1,5 @@
--- ---
title: Архитектура title: Архитектура
description: "Раздел описывает архитектуру проекта: из каких слоёв состоит приложение, как организован код внутри слоёв и какие правила управляют зависимостями."
--- ---
# SLM Design # SLM Design

View File

@@ -4,7 +4,7 @@ title: Слои
# Слои # Слои
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом. Слои SLM: назначение, классификация, направление зависимостей, правила.
## Определение ## Определение

View File

@@ -4,7 +4,7 @@ title: Модули
# Модули # Модули
Раздел описывает модули SLM: что такое модуль, из чего он состоит и как взаимодействует с остальным кодом. Модули SLM: состав, границы, взаимодействие с остальным кодом.
## Определение ## Определение
@@ -52,7 +52,7 @@ auth/
└── index.ts # публичный API └── index.ts # публичный API
``` ```
Подробное описание каждого сегмента — в разделе [Сегменты](/ru/basics/architecture/reference/segments). Подробное описание каждого сегмента — в разделе [Сегменты](/docs/basics/architecture/reference/segments).
## Публичный API ## Публичный API

View File

@@ -4,7 +4,7 @@ title: Сегменты
# Сегменты # Сегменты
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит. Сегменты SLM: типы, назначение, что лежит внутри каждого.
## Определение ## Определение

View File

@@ -4,7 +4,7 @@ title: Стиль кода
# Стиль кода # Стиль кода
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость. Единые правила оформления кода: форматирование, импорты, читаемость.
## Отступы ## Отступы

View File

@@ -4,8 +4,7 @@ title: Документирование
# Документирование # Документирование
Этот раздел описывает правила документирования кода: когда и как писать Правила документирования кода: что и когда документировать через JSDoc.
комментарии к компонентам, функциям, типам и интерфейсам.
## Общие правила ## Общие правила

View File

@@ -4,7 +4,7 @@ title: Именование
# Именование # Именование
Этот раздел описывает соглашения об именовании в проекте. Единые правила делают код предсказуемым и упрощают навигацию по проекту. Соглашения об именовании в коде: что и как называть.
## Базовые правила ## Базовые правила

View File

@@ -4,7 +4,7 @@ title: Технологии и библиотеки
# Технологии и библиотеки # Технологии и библиотеки
Этот раздел описывает базовый стек технологий и библиотек, принятый в проекте. Базовый стек проекта по областям: UI, архитектура, данные, состояние, локализация, тестирование, стили, генерация кода.
## Что используем ## Что используем
@@ -13,7 +13,7 @@ title: Технологии и библиотеки
- `Next.js` — для продуктовых сайтов. - `Next.js` — для продуктовых сайтов.
### Архитектура ### Архитектура
- `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/ru/basics/architecture/). - `SLM Design` — собственная модульная архитектура проекта. Подробнее в разделе [Архитектура](/docs/basics/architecture/).
### UI компоненты ### UI компоненты
- `Mantine UI` — базовые UI-компоненты. - `Mantine UI` — базовые UI-компоненты.

View File

@@ -4,7 +4,7 @@ title: Типизация
# Типизация # Типизация
Этот раздел описывает правила типизации: как типизировать компоненты, функции и работу с `any`/`unknown`. Правила типизации в TypeScript: общие принципы и работа с динамическими типами.
## Общие правила ## Общие правила

View File

@@ -1,11 +1,17 @@
# NextJS Style Guide # NextJS Style Guide
Правила и стандарты разработки на NextJS и TypeScript: архитектура, типизация, стили, компоненты, API и инфраструктурные разделы. Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.
## Для ассистентов ## Использование
Карта документации со ссылками на все разделы (формат [llmstxt.org](https://llmstxt.org)): **Для AI-агентов:**
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
- [llms.txt](/llms.txt) — Карта разделов, оглавление со ссылками на разделы.
- [llms-full.txt](/llms-full.txt) — Вся документация одним файлом.
**Для проекта:**
- [nextjs-style-guide.zip](/nextjs-style-guide.zip) — Набор Markdown-файлов для распаковки в `./ai/nextjs-style-guide/` или другую папку проекта.
## Структура документации ## Структура документации
@@ -59,3 +65,5 @@ https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/ru/llms.txt
| Шрифты | _(не заполнен)_ | | Шрифты | _(не заполнен)_ |
| Локализация | _(не заполнен)_ | | Локализация | _(не заполнен)_ |

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ title: Начало работы
**Next.js** (App Router), **Mantine**, **Zustand**, **SLM Design**. **Next.js** (App Router), **Mantine**, **Zustand**, **SLM Design**.
Подробнее — [Технологии и библиотеки](/ru/basics/tech-stack). Подробнее — [Технологии и библиотеки](/docs/basics/tech-stack).
## Ключевые особенности ## Ключевые особенности
@@ -19,4 +19,4 @@ title: Начало работы
## Настройка окружения ## Настройка окружения
Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/ru/applied/vscode). Открыть проект в VS Code и установить рекомендуемые расширения — редактор предложит это автоматически. Подробнее — [Настройка VS Code](/docs/applied/vscode).

View File

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

View File

@@ -1,5 +0,0 @@
---
title: API
---
# API

View File

@@ -1,7 +0,0 @@
---
title: Components
---
# Components
Rules for creating UI components across all FSD layers.

View File

@@ -1,5 +0,0 @@
---
title: Fonts
---
# Fonts

View File

@@ -1,5 +0,0 @@
---
title: Hooks
---
# Hooks

View File

@@ -1,5 +0,0 @@
---
title: Images
---
# Images

View File

@@ -1,5 +0,0 @@
---
title: Localization
---
# Localization

View File

@@ -1,7 +0,0 @@
---
title: Page-level Components
---
# Page-level Components
Next.js App Router special files used by the framework by convention: `layout.tsx`, `page.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`, `template.tsx`.

View File

@@ -1,7 +0,0 @@
---
title: Project Structure
---
# Project Structure
Base project structure and principles of module organization at folder and file level.

View File

@@ -1,5 +0,0 @@
---
title: Stores
---
# Stores

View File

@@ -1,7 +0,0 @@
---
title: Styles
---
# Styles
CSS writing rules: PostCSS Modules, nesting, media queries, variables, formatting.

View File

@@ -1,5 +0,0 @@
---
title: SVG Sprites
---
# SVG Sprites

View File

@@ -1,7 +0,0 @@
---
title: Templates & Code Generation
---
# Templates & Code Generation
Template tools, syntax, and examples for code generation.

View File

@@ -1,5 +0,0 @@
---
title: Video
---
# Video

View File

@@ -1,7 +0,0 @@
---
title: Architecture
---
# Architecture
Architecture based on FSD (Feature-Sliced Design) and strict module boundaries.

View File

@@ -1,7 +0,0 @@
---
title: Code Style
---
# Code Style
Unified code formatting rules: indentation, line breaks, quotes, import order, and readability.

View File

@@ -1,7 +0,0 @@
---
title: Documentation
---
# Documentation
Documentation should help understand the purpose of an entity, not duplicate its types or obvious details.

View File

@@ -1,7 +0,0 @@
---
title: Naming
---
# Naming
Naming should be predictable, concise, and reflect the meaning of the entity.

View File

@@ -1,7 +0,0 @@
---
title: Tech Stack
---
# Tech Stack
Base technology stack and libraries used in projects.

View File

@@ -1,7 +0,0 @@
---
title: Typing
---
# Typing
Typing is required for all public interfaces, functions, and components.

View File

@@ -1,58 +0,0 @@
# NextJS Style Guide
Rules and standards for NextJS and TypeScript development: architecture, typing, styles, components, API, and infrastructure.
## Documentation Structure
### Processes
**What to do** in a specific situation — step-by-step instructions.
| Section | Answers the question |
|---------|---------------------|
| Getting Started | What tools to install before starting development? |
| Creating an App | How to create a new project, where to get a template? |
| Creating Pages | How to add a page: routing and screen? |
| Creating Components | How to generate components using templates? |
| Styling | What to use: Mantine, tokens, or PostCSS? |
| Data Fetching | How to fetch data: SWR, codegen, sockets? |
| State Management | When and how to create a store (Zustand)? |
| Localization | How to add translations and work with i18next? |
### Basic Rules
**What the code should look like** — standards not tied to a specific technology.
| Section | Answers the question |
|---------|---------------------|
| Tech Stack | What stack do we use? |
| Architecture | How are FSD layers, dependencies, and public API structured? |
| Code Style | How to format code: indentation, quotes, imports, early return? |
| Naming | How to name files, variables, components, hooks? |
| Documentation | How to write JSDoc: what to document and what not? |
| Typing | How to type: type vs interface, any/unknown? |
### Applied Sections
**How a specific area works** — rules, structure, and code examples for specific technologies and tools.
| Section | Answers the question |
|---------|---------------------|
| Project Structure | How are folders and files organized by FSD? |
| Components | How is a component structured: files, props, clsx? |
| Page-level Components | How to define layout, page, loading, error, not-found? |
| Templates & Code Generation | How do templates work: syntax, variables, modifiers? |
| Styles | How to write CSS: PostCSS Modules, nesting, media, tokens? |
| Images | _(not filled)_ |
| SVG Sprites | _(not filled)_ |
| Video | _(not filled)_ |
| API | _(not filled)_ |
| Stores | _(not filled)_ |
| Hooks | _(not filled)_ |
| Fonts | _(not filled)_ |
| Localization | _(not filled)_ |
## For Assistants
Documentation map with links to all sections ([llmstxt.org](https://llmstxt.org) format):
https://gromlab.ru/docs/nextjs-style-guide/raw/branch/main/generated/en/llms.txt

View File

@@ -1,7 +0,0 @@
---
title: Creating an App
---
# Creating an App
How to create a new application: choosing a project template and initialization.

View File

@@ -1,7 +0,0 @@
---
title: Creating Components
---
# Creating Components
Generating components using templates, working with child components.

View File

@@ -1,7 +0,0 @@
---
title: Creating Pages
---
# Creating Pages
Page creation pattern: routing (page.tsx) and screen.

View File

@@ -1,7 +0,0 @@
---
title: Data Fetching
---
# Data Fetching
How to fetch data: SWR, API client codegen, sockets.

View File

@@ -1,7 +0,0 @@
---
title: Getting Started
---
# Getting Started
Setting up the environment and installing tools before starting development.

View File

@@ -1,7 +0,0 @@
---
title: Localization
---
# Localization
How to add translations and work with i18next.

View File

@@ -1,7 +0,0 @@
---
title: State Management
---
# State Management
When and how to create a store (Zustand), what to store locally vs globally.

View File

@@ -1,7 +0,0 @@
---
title: Styling
---
# Styling
Styling tools priority and rules for their application.

View File

@@ -3,96 +3,21 @@ layout: false
--- ---
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, onMounted } from 'vue'
const STORAGE_KEY = 'nsg-landing-lang'
const THEME_KEY = 'vitepress-theme-appearance' const THEME_KEY = 'vitepress-theme-appearance'
// __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION` // __BUILD_VERSION__ подставляется Vite-define из ENV `BUILD_VERSION`
// (см. .vitepress/config.ts). В dev и build всегда определена. // (см. .vitepress/config.ts). В dev и build всегда определена.
const buildVersion = __BUILD_VERSION__ const buildVersion = __BUILD_VERSION__
const dict = {
ru: {
tagline: 'Готовые соглашения по архитектуре, коду, компонентам и инфраструктуре для Next.js + TypeScript-проектов — чтобы команда писала одинаково, а новые разработчики включались в проект быстрее.',
langLabel: 'Язык',
themeLabel: 'Тема',
themes: { auto: 'Авто', light: 'Светлая', dark: 'Тёмная' },
cards: {
docs: {
title: 'Документация',
desc: 'Все разделы: процессы разработки, базовые правила, прикладные руководства.',
href: './ru/',
cta: 'Открыть',
},
ai: {
title: 'Ассистенту',
desc: 'Карта документации в формате llms.txt для AI-агентов.',
href: './ru/llms.txt',
cta: 'Открыть',
},
zip: {
title: 'Скачать правила',
desc: 'Архив всех Markdown-файлов одним ZIP.',
href: './nextjs-style-guide-ru.zip',
cta: 'Скачать',
},
},
},
en: {
tagline: 'Ready-made standards for architecture, code, components, and infrastructure in Next.js + TypeScript projects — so your team writes consistently and new developers ramp up faster.',
langLabel: 'Language',
themeLabel: 'Theme',
themes: { auto: 'Auto', light: 'Light', dark: 'Dark' },
cards: {
docs: {
title: 'Documentation',
desc: 'All sections: development processes, basic rules, applied guides.',
href: '#',
cta: 'Open',
badge: 'in development',
},
ai: {
title: 'For Assistant',
desc: 'Documentation map in llms.txt format for AI agents.',
href: '#',
cta: 'Open',
badge: 'in development',
},
zip: {
title: 'Download rules',
desc: 'Archive of all Markdown files and llms.txt in a single ZIP.',
href: '#',
cta: 'Download',
badge: 'soon',
},
},
},
}
const lang = ref('ru')
const theme = ref('auto') const theme = ref('auto')
onMounted(() => { onMounted(() => {
const savedLang = localStorage.getItem(STORAGE_KEY)
if (savedLang === 'ru' || savedLang === 'en') {
lang.value = savedLang
} else {
const nav = (navigator.language || 'ru').toLowerCase()
lang.value = nav.startsWith('ru') ? 'ru' : 'en'
}
const savedTheme = localStorage.getItem(THEME_KEY) const savedTheme = localStorage.getItem(THEME_KEY)
theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto' theme.value = savedTheme === 'dark' || savedTheme === 'light' ? savedTheme : 'auto'
}) })
const t = computed(() => dict[lang.value])
function setLang(value) {
lang.value = value
localStorage.setItem(STORAGE_KEY, value)
}
function setTheme(value) { function setTheme(value) {
theme.value = value theme.value = value
if (value === 'auto') { if (value === 'auto') {
@@ -118,31 +43,26 @@ function toggleTheme(value) {
<section class="landing__hero"> <section class="landing__hero">
<h1 class="landing__title">NextJS Style Guide</h1> <h1 class="landing__title">NextJS Style Guide</h1>
<ClientOnly> <ClientOnly>
<p class="landing__tagline">{{ t.tagline }}</p> <p class="landing__tagline">Соглашения по разработке Next.js проектов: архитектура и слои приложения, структура кода, организация модулей, стилизация, типизация и инфраструктура.</p>
<div class="landing__controls"> <div class="landing__controls">
<div class="seg" role="group" :aria-label="t.langLabel"> <a
<button class="landing__repo"
type="button" href="https://gromlab.ru/docs/nextjs-style-guide"
class="seg__btn" target="_blank"
:class="{ 'seg__btn--active': lang === 'ru' }" rel="noopener"
:aria-pressed="lang === 'ru'" >
@click="setLang('ru')" <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
>Русский</button> <path d="M12 .3a12 12 0 0 0-3.8 23.38c.6.12.83-.26.83-.57L9 21.07c-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.08-.74.09-.73.09-.73 1.2.09 1.83 1.24 1.83 1.24 1.08 1.83 2.81 1.3 3.5 1 .1-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.14-.3-.54-1.52.1-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.64 1.66.24 2.88.12 3.18a4.65 4.65 0 0 1 1.23 3.22c0 4.61-2.8 5.63-5.48 5.92.42.36.81 1.1.81 2.22l-.01 3.29c0 .31.2.69.82.57A12 12 0 0 0 12 .3Z"/>
<button </svg>
type="button" <span>Репозиторий</span>
class="seg__btn" </a>
:class="{ 'seg__btn--active': lang === 'en' }" <div class="seg seg--icons" role="group" aria-label="Тема">
:aria-pressed="lang === 'en'"
@click="setLang('en')"
>English</button>
</div>
<div class="seg seg--icons" role="group" :aria-label="t.themeLabel">
<button <button
type="button" type="button"
class="seg__btn" class="seg__btn"
:class="{ 'seg__btn--active': theme === 'light' }" :class="{ 'seg__btn--active': theme === 'light' }"
:aria-pressed="theme === 'light'" :aria-pressed="theme === 'light'"
:title="t.themes.light" title="Светлая"
@click="toggleTheme('light')" @click="toggleTheme('light')"
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
@@ -152,7 +72,7 @@ function toggleTheme(value) {
class="seg__btn" class="seg__btn"
:class="{ 'seg__btn--active': theme === 'dark' }" :class="{ 'seg__btn--active': theme === 'dark' }"
:aria-pressed="theme === 'dark'" :aria-pressed="theme === 'dark'"
:title="t.themes.dark" title="Тёмная"
@click="toggleTheme('dark')" @click="toggleTheme('dark')"
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
@@ -162,25 +82,26 @@ function toggleTheme(value) {
</ClientOnly> </ClientOnly>
</section> </section>
<ClientOnly>
<section class="landing__cards"> <section class="landing__cards">
<a <a class="landing__card" href="./docs/">
v-for="key in ['docs', 'ai', 'zip']" <h3>Документация</h3>
:key="key" <p>Все разделы: процессы разработки, базовые правила, прикладные руководства.</p>
class="landing__card" <span class="landing__cta">Открыть →</span>
:class="{ 'landing__card--soon': t.cards[key].badge }" </a>
:href="t.cards[key].href" <div class="landing__card landing__card--multi">
:aria-disabled="t.cards[key].badge ? 'true' : null" <h3>Ассистенту</h3>
> <p>Карта документации в формате llms.txt для AI-агентов.</p>
<h3> <div class="landing__buttons">
{{ t.cards[key].title }} <a class="landing__button" href="./llms.txt" target="_blank" rel="noopener">llms.txt</a>
<span v-if="t.cards[key].badge" class="landing__badge">{{ t.cards[key].badge }}</span> <a class="landing__button" href="./llms-full.txt" target="_blank" rel="noopener">llms-full.txt</a>
</h3> </div>
<p>{{ t.cards[key].desc }}</p> </div>
<span class="landing__cta">{{ t.cards[key].cta }} →</span> <a class="landing__card" href="./nextjs-style-guide.zip">
<h3>Скачать правила</h3>
<p>Архив всех Markdown-файлов одним ZIP.</p>
<span class="landing__cta">Скачать →</span>
</a> </a>
</section> </section>
</ClientOnly>
<p class="landing__version">v{{ buildVersion }}</p> <p class="landing__version">v{{ buildVersion }}</p>
</div> </div>
@@ -207,6 +128,35 @@ function toggleTheme(value) {
flex-wrap: wrap; flex-wrap: wrap;
} }
.landing__controls > * {
height: 36px;
box-sizing: border-box;
}
.landing__repo {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0 14px;
border: 1px solid var(--vp-c-divider);
border-radius: 999px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
font-size: 13px;
font-weight: 500;
text-decoration: none;
transition: color 0.15s, border-color 0.15s;
}
.landing__repo:hover {
color: var(--vp-c-text-1);
border-color: var(--vp-c-brand-1);
}
.landing__repo svg {
flex-shrink: 0;
}
.seg { .seg {
display: inline-flex; display: inline-flex;
align-items: stretch; align-items: stretch;
@@ -305,12 +255,6 @@ function toggleTheme(value) {
transform: translateY(-2px); transform: translateY(-2px);
} }
.landing__card--soon {
opacity: 0.55;
cursor: not-allowed;
pointer-events: none;
}
.landing__card h3 { .landing__card h3 {
margin: 0; margin: 0;
font-size: 18px; font-size: 18px;
@@ -332,6 +276,33 @@ function toggleTheme(value) {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
.landing__buttons {
margin-top: auto;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.landing__button {
display: inline-flex;
align-items: center;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
font-family: var(--vp-font-family-mono, monospace);
color: var(--vp-c-text-1);
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
text-decoration: none;
transition: border-color 0.15s, color 0.15s;
}
.landing__button:hover {
border-color: var(--vp-c-brand-1);
color: var(--vp-c-brand-1);
}
.landing__version { .landing__version {
text-align: center; text-align: center;
margin: 24px 0 0; margin: 24px 0 0;
@@ -340,33 +311,58 @@ function toggleTheme(value) {
font-family: var(--vp-font-family-mono, monospace); font-family: var(--vp-font-family-mono, monospace);
} }
.landing__badge {
display: inline-block;
margin-left: 8px;
padding: 2px 8px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
border-radius: 999px;
background: var(--vp-c-bg-mute);
color: var(--vp-c-text-3);
vertical-align: middle;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.landing { .landing {
padding: 16px 16px 48px; padding: 48px 20px 56px;
gap: 32px; gap: 40px;
justify-content: flex-start;
} }
.landing__title { .landing__title {
font-size: 36px; font-size: 36px;
} }
.landing__tagline {
font-size: 16px;
}
.landing__cards { .landing__cards {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 16px;
} }
.landing__controls { .landing__controls {
gap: 8px; gap: 8px;
margin-top: 36px;
}
}
@media (max-width: 480px) {
.landing {
padding: 44px 16px 48px;
gap: 36px;
}
.landing__title {
font-size: 30px;
}
.landing__tagline {
font-size: 15px;
line-height: 1.5;
}
.landing__controls {
margin-top: 32px;
}
/* Репозиторий — только иконка, без текста, чтобы все контролы влезли в строку */
.landing__repo {
width: 36px;
padding: 0;
justify-content: center;
}
.landing__repo span {
display: none;
}
.seg__btn {
padding: 6px 12px;
font-size: 12px;
}
.landing__card {
padding: 20px;
} }
} }
</style> </style>

View File

@@ -11,7 +11,8 @@ const BUILD_DATE = new Date().toISOString();
/** Корневая папка для генерируемой статики (попадает в build dist). */ /** Корневая папка для генерируемой статики (попадает в build dist). */
const PUBLIC_DIR = 'docs/public'; const PUBLIC_DIR = 'docs/public';
type Lang = 'ru' | 'en'; /** Префикс URL документации. Соответствует структуре `docs/docs/...`. */
const DOC_PREFIX = '/docs/';
interface SidebarItem { interface SidebarItem {
text: string; text: string;
@@ -76,13 +77,13 @@ const firstParagraphAfterH1 = (body: string): string | null => {
}; };
/** /**
* Преобразовать sidebar `link` в относительный путь файла внутри * Преобразовать sidebar `link` (например `/docs/foo`) в относительный
* `docs/{lang}/`. Sidebar links содержат полный префикс локали * путь файла внутри `docs/docs/`. Префикс `/docs/` отрезается.
* (`/ru/...`, `/en/...`) — отрезаем его.
*/ */
const linkToRel = (link: string, lang: Lang): string => { const linkToRel = (link: string): string => {
const prefix = `/${lang}/`; let rel = link.startsWith(DOC_PREFIX)
let rel = link.startsWith(prefix) ? link.slice(prefix.length) : link.replace(/^\//, ''); ? link.slice(DOC_PREFIX.length)
: link.replace(/^\//, '');
if (rel === '' || rel.endsWith('/')) { if (rel === '' || rel.endsWith('/')) {
rel += 'index.md'; rel += 'index.md';
} else { } else {
@@ -91,15 +92,12 @@ const linkToRel = (link: string, lang: Lang): string => {
return rel; return rel;
}; };
const linkToFilePath = (link: string, lang: Lang): string => const linkToFilePath = (link: string): string =>
path.join('docs', lang, linkToRel(link, lang)); path.join('docs/docs', linkToRel(link));
/** /** Абсолютный URL `.md`-копии страницы на сайте. */
* Абсолютный путь от корня сайта к `.md`-копии страницы. const linkToSiteUrl = (link: string): string =>
* После build файлы лежат в `dist/{lang}/...md` (через `docs/public/`). `${DOC_PREFIX}${linkToRel(link)}`;
*/
const linkToSiteUrl = (link: string, lang: Lang): string =>
`/${lang}/${linkToRel(link, lang)}`;
/** /**
* Развернуть sidebar в плоский список с сохранением группы и * Развернуть sidebar в плоский список с сохранением группы и
@@ -153,34 +151,20 @@ const groupBySection = (entries: Entry[]): Map<string, Entry[]> => {
return map; return map;
}; };
const buildLlms = (lang: Lang): void => { interface SiteConfig {
const localeKey = lang;
// VitePress-конфиг типизирован как `UserConfig`, но обращаемся к
// фактически переданным значениям — сужаем тип через any.
const cfg = config as unknown as {
title: string; title: string;
description: string; description: string;
locales: Record< themeConfig: { sidebar: SidebarItem[] };
string,
{
description?: string;
llmsBlockquote?: string; llmsBlockquote?: string;
llmsContext?: string; llmsContext?: string;
themeConfig?: { sidebar?: SidebarItem[] };
} }
>;
};
const locale = cfg.locales[localeKey]; const cfg = config as unknown as SiteConfig;
const sidebar = locale?.themeConfig?.sidebar;
if (!sidebar) { const buildLlms = (): void => {
console.warn(`[${lang}] sidebar не найден в config`); const sidebar = cfg.themeConfig.sidebar;
return; const blockquote = cfg.llmsBlockquote ?? cfg.description;
} const context = cfg.llmsContext;
// Для blockquote предпочитаем расширенный llms-текст; короткий
// description — fallback и используется для HTML meta-тега VitePress.
const blockquote = locale.llmsBlockquote ?? locale.description ?? cfg.description;
const context = locale.llmsContext;
const entries = flattenSidebar(sidebar); const entries = flattenSidebar(sidebar);
const grouped = groupBySection(entries); const grouped = groupBySection(entries);
@@ -200,19 +184,16 @@ const buildLlms = (lang: Lang): void => {
lines.push(''); lines.push('');
for (const entry of items) { for (const entry of items) {
const filePath = linkToFilePath(entry.link, lang); const filePath = linkToFilePath(entry.link);
const url = linkToSiteUrl(entry.link, lang); const url = linkToSiteUrl(entry.link);
// Текст ссылки берём из sidebar — он специально написан для навигации
// и точнее отражает иерархию (например "Обзор" внутри группы "Архитектура").
let description: string | null = null; let description: string | null = null;
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8'); const raw = fs.readFileSync(filePath, 'utf8');
const { data, body } = parseFrontmatter(raw); const { data, body } = parseFrontmatter(raw);
description = data.description || firstParagraphAfterH1(body); description = data.description || firstParagraphAfterH1(body);
} else { } else {
console.warn(`[${lang}] файл не найден: ${filePath}`); console.warn(`файл не найден: ${filePath}`);
} }
const display = entry.prefix const display = entry.prefix
@@ -225,40 +206,6 @@ const buildLlms = (lang: Lang): void => {
lines.push(''); lines.push('');
} }
const outDir = path.join(PUBLIC_DIR, lang);
fs.mkdirSync(outDir, { recursive: true });
const outFile = path.join(outDir, 'llms.txt');
fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
console.log(`${outFile} создан`);
};
/**
* Корневой `/llms.txt` — роутер. По стандарту llmstxt.org это
* единственный файл в корне сайта; для двуязычного проекта он
* указывает LLM на локализованные карты документации.
*/
const buildRootIndex = (): void => {
const cfg = config as unknown as {
title: string;
description: string;
locales: Record<string, { description?: string }>;
};
const ruDesc = cfg.locales.ru?.description ?? cfg.description;
const enDesc = cfg.locales.en?.description ?? cfg.description;
const lines: string[] = [
`# ${cfg.title}`,
'',
`> ${enDesc}.`,
'',
'## Documentation',
'',
`- [Русская версия (Russian)](/ru/llms.txt): ${ruDesc}.`,
'- English version: in development',
'',
];
fs.mkdirSync(PUBLIC_DIR, { recursive: true }); fs.mkdirSync(PUBLIC_DIR, { recursive: true });
const outFile = path.join(PUBLIC_DIR, 'llms.txt'); const outFile = path.join(PUBLIC_DIR, 'llms.txt');
fs.writeFileSync(outFile, lines.join('\n'), 'utf8'); fs.writeFileSync(outFile, lines.join('\n'), 'utf8');
@@ -288,41 +235,169 @@ const copyDirSync = (
}; };
/** /**
* Скопировать все `.md`-файлы локали в `docs/public/{lang}/`, * Скопировать все `.md`-файлы документации в `docs/public/docs/`,
* чтобы они попали в build `dist/` и были доступны по URL `/lang/path.md`. * чтобы они попали в build `dist/` и были доступны по URL `/docs/path.md`.
*/ */
const copyMdFiles = (lang: Lang): void => { const copyMdFiles = (): void => {
const srcDir = path.join('docs', lang); const srcDir = 'docs/docs';
const destDir = path.join(PUBLIC_DIR, lang); const destDir = path.join(PUBLIC_DIR, 'docs');
if (!fs.existsSync(srcDir)) return; if (!fs.existsSync(srcDir)) return;
const copied = copyDirSync(srcDir, destDir, (name) => name.endsWith('.md')); const copied = copyDirSync(srcDir, destDir, (name) => name.endsWith('.md'));
console.log(`[${lang}] скопировано ${copied} .md-файлов в ${destDir}`); console.log(`скопировано ${copied} .md-файлов в ${destDir}`);
}; };
/** /**
* Собрать `nextjs-style-guide-{lang}.zip` со всеми `.md` локали и `VERSION`. * Преобразовать sidebar `link` в относительный путь файла внутри архива
* Внутри архива — единая папка `nextjs-style-guide/`. * (от корня папки `nextjs-style-guide/`).
*
* `llms.txt` в архив не кладём: его ссылки указывают на сайт и локально
* не работают. Структура папки сама по себе является картой документации.
*/ */
const buildZip = (lang: Lang): void => { const linkToArchiveRel = (link: string): string => {
let rel = link.startsWith(DOC_PREFIX)
? link.slice(DOC_PREFIX.length)
: link.replace(/^\//, '');
if (rel === '' || rel.endsWith('/')) {
rel += 'index.md';
} else {
rel += '.md';
}
return rel;
};
/**
* Заменить во всех `.md` архива ссылки `[text](/docs/foo)` на относительные
* пути от расположения файла. Без этого внутренние ссылки в распакованной
* папке не работают.
*/
const transformLinksInDir = (rootDir: string): void => {
const linkRe = /\]\(\/docs\/([^)\s#]*)(#[^)]*)?\)/g;
const walk = (dir: string): void => {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
continue;
}
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
const content = fs.readFileSync(full, 'utf8');
const fileDir = path.dirname(full);
const updated = content.replace(linkRe, (_match, route, hash = '') => {
const targetRel = linkToArchiveRel(`${DOC_PREFIX}${route}`);
const targetAbs = path.join(rootDir, targetRel);
let rel = path.relative(fileDir, targetAbs);
if (!rel.startsWith('.')) rel = './' + rel;
return `](${rel}${hash})`;
});
if (updated !== content) {
fs.writeFileSync(full, updated, 'utf8');
}
}
};
walk(rootDir);
};
/**
* Сгенерировать `README.md` — точка входа архива. Карта документации
* с относительными ссылками, описаниями из frontmatter/первого абзаца
* и метаинфо сборки.
*/
const buildArchiveReadme = (rootDir: string): void => {
const sidebar = cfg.themeConfig.sidebar;
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
const context = cfg.llmsContext;
const entries = flattenSidebar(sidebar).filter(
// «Главная» из sidebar — это страница раздела для веба, в архиве не нужна.
(e) => e.section !== 'Главная',
);
const grouped = groupBySection(entries);
const lines: string[] = [];
lines.push(`# ${cfg.title}`);
lines.push('');
if (blockquote) {
lines.push(`> ${blockquote}`);
lines.push('');
}
if (context) {
lines.push(context);
lines.push('');
}
lines.push('## Содержание');
lines.push('');
for (const [section, items] of grouped) {
lines.push(`### ${section}`);
lines.push('');
for (const entry of items) {
const targetRel = './' + linkToArchiveRel(entry.link);
const filePath = path.join(rootDir, linkToArchiveRel(entry.link));
let description: string | null = null;
if (fs.existsSync(filePath)) {
const raw = fs.readFileSync(filePath, 'utf8');
const { data, body } = parseFrontmatter(raw);
description = data.description || firstParagraphAfterH1(body);
}
const display = entry.prefix
? `${entry.prefix}: ${entry.text}`
: entry.text;
const descPart = description ? `${description}` : '';
lines.push(`- [${display}](${targetRel})${descPart}`);
}
lines.push('');
}
lines.push('---');
lines.push('');
lines.push(`Версия: ${VERSION} · Сборка: ${BUILD_DATE}`);
lines.push('');
fs.writeFileSync(path.join(rootDir, 'README.md'), lines.join('\n'), 'utf8');
};
/**
* Собрать `nextjs-style-guide.zip`. Внутри — папка `nextjs-style-guide/`
* с `.md`-файлами, README, `llms-full.txt` и `VERSION`. Внутренние ссылки
* преобразуются в относительные.
*/
const buildZip = (): void => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-')); const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nsg-'));
const stage = path.join(tmpRoot, 'nextjs-style-guide'); const stage = path.join(tmpRoot, 'nextjs-style-guide');
fs.mkdirSync(stage, { recursive: true }); fs.mkdirSync(stage, { recursive: true });
copyDirSync(path.join('docs', lang), stage, (name) => name.endsWith('.md')); // 1. Копируем все .md в staging.
copyDirSync('docs/docs', stage, (name) => name.endsWith('.md'));
// 2. Удаляем веб-index.md — в архиве его роль выполняет README.md.
const indexPath = path.join(stage, 'index.md');
if (fs.existsSync(indexPath)) fs.unlinkSync(indexPath);
// 3. Преобразуем абсолютные ссылки `/docs/...` в относительные.
transformLinksInDir(stage);
// 4. Генерируем точку входа README.md.
buildArchiveReadme(stage);
// 5. Кладём llms-full.txt — удобно для одноразового чтения LLM.
const llmsFullSrc = path.join(PUBLIC_DIR, 'llms-full.txt');
if (fs.existsSync(llmsFullSrc)) {
fs.copyFileSync(llmsFullSrc, path.join(stage, 'llms-full.txt'));
}
// 6. Метаинформация сборки.
fs.writeFileSync( fs.writeFileSync(
path.join(stage, 'VERSION'), path.join(stage, 'VERSION'),
`${VERSION}\n${BUILD_DATE}\n`, `${VERSION}\n${BUILD_DATE}\n`,
); );
const outFile = path.resolve( const outFile = path.resolve(PUBLIC_DIR, 'nextjs-style-guide.zip');
PUBLIC_DIR,
`nextjs-style-guide-${lang}.zip`,
);
fs.rmSync(outFile, { force: true }); fs.rmSync(outFile, { force: true });
execFileSync('zip', ['-rq', outFile, 'nextjs-style-guide'], { execFileSync('zip', ['-rq', outFile, 'nextjs-style-guide'], {
@@ -333,21 +408,75 @@ const buildZip = (lang: Lang): void => {
console.log(`${outFile} создан (${VERSION})`); console.log(`${outFile} создан (${VERSION})`);
}; };
/** Удалить YAML frontmatter из исходника `.md`. */
const stripFrontmatter = (content: string): string =>
content.replace(/^---\n[\s\S]*?\n---\n*/, '');
/**
* Сдвинуть уровень заголовков на 1 вниз (h1→h2, h2→h3, ...).
* Игнорирует строки внутри блоков кода.
*/
const shiftHeadings = (content: string): string => {
const lines = content.split('\n');
let inCodeBlock = false;
return lines
.map((line) => {
if (line.startsWith('```')) inCodeBlock = !inCodeBlock;
if (inCodeBlock) return line;
if (/^#{1,5}\s/.test(line)) return '#' + line;
return line;
})
.join('\n');
};
/**
* Собрать `llms-full.txt` — все страницы в одном файле.
* Порядок страниц повторяет порядок в sidebar.
*/
const buildLlmsFull = (): void => {
const sidebar = cfg.themeConfig.sidebar;
const entries = flattenSidebar(sidebar);
const blockquote = cfg.llmsBlockquote ?? cfg.description ?? '';
const parts: string[] = [];
parts.push(`# ${cfg.title}`);
parts.push('');
if (blockquote) parts.push(`> ${blockquote}`);
if (cfg.llmsContext) {
parts.push('');
parts.push(cfg.llmsContext);
}
parts.push('');
for (const entry of entries) {
const filePath = linkToFilePath(entry.link);
if (!fs.existsSync(filePath)) continue;
const raw = fs.readFileSync(filePath, 'utf8');
const content = shiftHeadings(stripFrontmatter(raw)).trim();
if (!content) continue;
// Мета-якорь: путь страницы для ориентации LLM
parts.push(`<!-- ${entry.link} -->`);
parts.push('');
parts.push(content);
parts.push('');
}
fs.mkdirSync(PUBLIC_DIR, { recursive: true });
const outFile = path.join(PUBLIC_DIR, 'llms-full.txt');
fs.writeFileSync(outFile, parts.join('\n'), 'utf8');
console.log(`${outFile} создан`);
};
/** Манифест сборки — для лендинга и внешних потребителей. */ /** Манифест сборки — для лендинга и внешних потребителей. */
const writeManifest = (): void => { const writeManifest = (): void => {
const manifest = { const manifest = {
version: VERSION, version: VERSION,
buildDate: BUILD_DATE, buildDate: BUILD_DATE,
languages: { llms: '/llms.txt',
ru: { llmsFull: '/llms-full.txt',
llms: '/ru/llms.txt', zip: '/nextjs-style-guide.zip',
zip: '/nextjs-style-guide-ru.zip',
},
en: {
llms: '/en/llms.txt',
zip: '/nextjs-style-guide-en.zip',
},
},
}; };
fs.mkdirSync(PUBLIC_DIR, { recursive: true }); fs.mkdirSync(PUBLIC_DIR, { recursive: true });
fs.writeFileSync( fs.writeFileSync(
@@ -358,26 +487,22 @@ const writeManifest = (): void => {
console.log(`${PUBLIC_DIR}/manifest.json создан`); console.log(`${PUBLIC_DIR}/manifest.json создан`);
}; };
/** Скопировать `index.md` локали в корневой README без frontmatter */ /** Скопировать `index.md` документации в корневой README без frontmatter. */
const buildReadme = (lang: Lang, outFile: string): void => { const buildReadme = (): void => {
const indexPath = path.join('docs', lang, 'index.md'); const indexPath = 'docs/docs/index.md';
if (!fs.existsSync(indexPath)) { if (!fs.existsSync(indexPath)) {
console.warn(`Пропуск ${outFile}: ${indexPath} не найден`); console.warn(`Пропуск README.md: ${indexPath} не найден`);
return; return;
} }
const raw = fs.readFileSync(indexPath, 'utf8'); const raw = fs.readFileSync(indexPath, 'utf8');
const { body } = parseFrontmatter(raw); const { body } = parseFrontmatter(raw);
fs.writeFileSync(outFile, body.trimStart(), 'utf8'); fs.writeFileSync('README.md', body.trimStart(), 'utf8');
console.log(`${outFile} обновлён из ${indexPath}`); console.log(`README.md обновлён из ${indexPath}`);
}; };
buildLlms('ru'); buildLlms();
buildLlms('en'); buildLlmsFull();
buildRootIndex(); copyMdFiles();
copyMdFiles('ru'); buildZip();
copyMdFiles('en');
buildZip('ru');
buildZip('en');
writeManifest(); writeManifest();
buildReadme('en', 'README.md'); buildReadme();
buildReadme('ru', 'README_RU.md');