Этот туториал — первая часть большого путешествия, в котором мы создадим AI-систему для автоматической генерации веб-страниц на React 19 и Next.js 15. Наша цель — не просто скорость, а архитектурная элегантность и идеальная консистентность дизайна.

План такой:
Часть 1 (Вы здесь): Разбираем базовую архитектуру: «жадные» маршруты, компонент‑трансформер и типизированные конфиги для стандартных страниц.
Часть 2: Усложняем задачу: генерация страниц документации и интерактивных туториалов.
Часть 3: Подключаем интеллект: настраиваем AI для автономной генерации контента.
Бонус: Практический кейс — разворачиваем, монетизируем и масштабируем реальное приложение.
Это пошаговая инструкция по созданию собственного v0-аналога корпоративного уровня с регистрациями и оплатами. Реальный бизнес на генерации красивых приложений с революционным интерфейсом от А до Я, в конце четвертой публикации . Чтобы собрать всю систему целиком, подписывайтесь. Дальше будет только интереснее.
Как всегда представляю исходный код готового решения чтобы вы могли сразу же приступить в созданию блогов магазинов лендингов и тп. Ссылка.
Технологический стек проекта: React 19, Next.js 15, Vercel, Prisma, Stripe, AI SDK, Chat GPT
Технологический стек сочетает стабильность и передовые возможности: React 19 и Next.js 15 с App Router обеспечивают молниеносный рендер и параллельные маршруты, Vercel гарантирует без‑доуночные деплои, а Prisma и Neon формируют типобезопасный доступ к данным. Stripe интегрирует мгновенные платежи, AI SDK с ChatGPT добавляет интеллектуальный слой, позволяя AIFA Dev Starter генерировать интерфейсы, контент и прототипы за считанные минуты, уже включая авторизацию и многоязычный AI‑чат.
На практике это сводится к простому циклу: вы отправляете промпт в ChatGPT, получаете в ответ конфигурационный файл, загружаете его в приложение и одной командой сборки создаёте готовые, стилизованные страницы. Всё это уже настроено в стартовом шаблоне, включая авторизацию и многоязычный AI‑чат.
Или используйте полную автоматизацию так же как в v0, но с прицелом под крупные корпоративные интеграции (в обновленном https://aifa.dev после третьей публикации).
Вот пример:

Кому выгодна AI-система генерации сайтов: студии, корпорации, стартапы, фрилансеры
Дизайн-студии нового формата: от макета к массовому запуску сайтов за минуты
Представьте дизайн-агентство, которое создает не просто красивые макеты, а целые технологические экосистемы. Один раз вложившись в разработку уникальных компонентов и фирменного стиля, дизайнеры получают возможность генерировать профессиональные сайты со скоростью 50+ проектов в час.
Это не фантастика — это новая бизнес-модель, где креатив встречается с автоматизацией. И да, монетизация уже встроена через Stripe — об этом расскажу в следующих частях!
Корпоративные внедрения: единый стиль и масштабируемость для Enterprise-сайтов
Классический кейс — QR-меню для сети ресторанов. Один раз создав профессиональную систему карточек блюд, владелец бизнеса может предложить готовое решение сотням заведений. Рестораны адаптируют только цвета под свой бренд и наполняют контентом голосом — а профессиональный дизайн и UX остаются неизменными.
Масштабируемость, единообразие, скорость внедрения — именно то, что нужно корпоративному сегменту в 2025 году.
Transformer-архитектура компонентов: JSON → React без ручной верстки
В основе технологии AIFA Dev лежат два критически важных компонента:
Интеллектуальный промпт-инжиниринг
Специально обученные инструкции для ИИ, способные извлекать из векторных баз данных точные примеры секций и адаптировать их под конкретные задачи.Компонентная архитектура‑трансформер
React‑компоненты, которые «понимают» конфигурационные файлы и превращают JSON‑описания в живые, интерактивные страницы.
Внешние модели (ChatGPT, Claude) отлично справляются с генерацией контента, но компоненты-распаковщики — это сердце системы, которое мы создаем собственными силами.
AI-ready документация: как использовать текст как базу знаний для ChatGPT
Не читайте этот туториал — используйте его как AI‑ассистента!
Основная ценность этого объемного материала не в последовательном чтении, а в том, что он становится персональной базой знаний для ваших проектов.
Как это работает:
Скопируйте весь текст в ChatGPT
Задавайте конкретные вопросы: «Как реализовать...?», «Зачем нужно...?», «Покажи код для...»
Получайте точные ответы с готовыми компонентами
Это не документация для чтения — это интерактивный справочник для быстрого решения задач. Попробуйте такой подход, и вы поймете, почему современная техническая документация должна быть AI‑ready!
1.Введение: цель, выгоды и место метода среди генераторов v0-поколения
Данный туториал не ставит своей задачей создание решения, превосходящего v0 по скорости генерации. Основная цель — обеспечение единообразия и гармоничности дизайна на всех страницах проекта.
Да, создание каждой отдельной страницы может потребовать несколько больше времени по сравнению с универсальными генераторами. Однако совокупные затраты на проект значительно снижаются благодаря следующим преимуществам:
Единый стиль всех компонентов — каждая новая страница автоматически вписывается в общую дизайн‑систему
Гармоничность всего проекта — отсутствие стилистических разрывов между разными разделами сайта
Снижение затрат на доработку — минимальная необходимость в последующей стилизации и адаптации
Масштабируемость решения — чем больше страниц, тем выше эффективность подхода
Результат: проект получается визуально целостным, профессиональным и требует минимальных усилий на поддержание единообразия интерфейса при масштабировании.
В основном туториале рассматривается создание стандартных страниц с использованием базовых компонентов header и footer.
Однако в исходном коде проекта вы найдете кастомные реализации для специализированных типов страниц:
Страницы документации с расширенной навигацией
Страницы Tutorial с интерактивными элементами
Работа со сложными компонентами:
Обратите внимание, что некоторые компоненты на главной странице обладают более сложной логикой:
Управление состоянием (state management)
Анимации и переходы
Интерактивные элементы
Интеграция продвинутых решений:
Для интеграции таких сложных компонентов предусмотрена возможность добавления Custom-компонентов вместо стандартных секций. Это позволяет:
Сохранить гибкость системы
Внедрить уникальную функциональность
Поддержать единый стиль даже для нестандартных решений
Практическое задание:
Изучение процесса создания и интеграции Custom-компонентов остается вашим практическим заданием для закрепления полученных знаний.
1.1.Концепция: единообразные страницы бренда, создаваемые ИИ по шаблонам
Современный процесс разработки требует скорости, однородности интерфейса и возможности быстрого реагирования на бизнес-требования. Мы предлагаем архитектуру, в которой стандартные, формализованные описания дизайн-секций (Hero, CTA, FAQ, Features и др.) хранятся в структурированной базе знаний ИИ.
Загружая туда ваши актуальные стандарты UI/UX для каждой секции, вы позволяете искусственному интеллекту выступать не только генератором контента, но и гарантом единого фирменного стиля на всех уровнях создания страниц.
1.2. Пять этапов: от загрузки дизайн-шаблонов до SEO-оптимизированного билда
Загрузка стандартов дизайна:
В базу знаний ИИ добавляются шаблоны и инструкции для каждой секции — как их визуально и структурно реализовывать в рамках единого стиля вашего бренда.Обработка текстового запроса:
Пользователь, менеджер проекта или программист формирует задание в виде текстового промпта — описывает желаемую страницу или её элементы.AI-интерпретация и структурирование:
Искусственный интеллект анализирует запрос, генерирует семантический контент, разбивает его на логические секции и подбирает подходящие шаблоны из базы знаний.Генерация кода страниц:
На основании выбранных шаблонов и сгенерированного контента система формирует строгие PageConfig’и — декларативные JSON/Type-описания будущих страниц. Они гарантируют соблюдение всех дизайн-стандартов и упрощают downstream-разработку.Автоматическая сборка и билдинг:
Готовые конфигурации применяются для статической генерации страниц с использованием рендер-компонентов, в результате чего вы получаете полноценную, SEO-оптимизированную страницу, визуально и технически выдержанную по вашим корпоративным стандартам.
1.3. Экономия ресурсов и рост конверсий: бизнес-преимущества AI-генерации
Быстрота вывода новых landing pages и product pages — достаточно промпта, чтобы ИИ сгенерировал страницу на основе актуальных шаблонов.
Единый фирменный стиль и качество — извлекается и соблюдается автоматически для каждой новой страницы.
Минимизация ручной работы и A/B тестирование — масштабирование вариантов страниц и изменение контента силами AI без участия разработчика в вёрстке.
Готовность к многоязычности — все тексты и элементы хранятся централизованно, легко поддаются локализации и адаптации.
Инфраструктурная гибкость — легко интегрируется в CI/CD пайплайны, современные хостинги и AI-driven процессы.
1.4. Как продолжить: дорожная карта из трёх статей и бонус-кейса
h4В данном туториале мы пошагово рассмотрим:
Архитектура существующих компонентов — детальный разбор структуры PageHtmlTransformer, системы обёрток (Wrapper/FullScreenWrapper), компонентов секций и их взаимодействия для понимания внутренних механизмов работы.
Принципы работы системы — изучение логики рендеринга секций, типизации конфигураций, механизмов роутинга и связи между конфигурационными файлами и React-компонентами. Это необходимо для того, чтобы разработчики могли самостоятельно расширять данные представления.
Создание инструкций для искусственного интеллекта — формирование структурированного описания существующих доступных типов для метаданных, header section, footer section. Добавление требований к созданию JSX fragment для body section с детальными спецификациями и ограничениями.
Генерация конфигурационных файлов — практическое применение полученных знаний для создания config-файлов, которые будут использоваться AI для автоматической генерации страниц с соблюдением всех архитектурных принципов и стандартов дизайна.
2. Структура проекта: папки, файлы и логика маршрутизации Next.js 15
Tree
app/@right/
├── public/
│ ├── (_routing)/
│ │ └── [[...slug]]/
│ │ └── page.tsx # 1. Динамический роутинг
│ └── (_service)/
│ ├── (_config)/
│ │ └── public-pages-config.ts # 6. Конфигурация публичных страниц
│ └── (_libs)/
│ └── utils.ts # 7. Утилиты
└── (_service)/
├── (_types)
│ ├── /page-transformer-custom-types/
│ └── page-wrapper-types.ts # 5. Типы и интерфейсы
└── (_components)/
└── page-transformer-components/
├── page-html-transformer.tsx # 2. Главный трансформер
├── custom-sections/
├── wrappers/
│ ├── full-screen-wrapper.tsx # 3. Полноэкранная обёртка
│ └── wrapper.tsx # 4. Стандартная обёртка
├── header-sections-components/
│ ├── header-section.tsx # 8. Компонент заголовка
│ ├── page-header-elements.tsx # 9. Элементы заголовка
│ └── announcement.tsx # 10. Компонент анонса
├── body-sections-components/
│ └── body-section.tsx # 12. Компонент Body
└── footer-sections-components/
└── footer-section.tsx # 11. Компонент футера
Components:
1-@/app/@right/public/(_routing)/[[…slug]]/page.tsx
2-@/app/@right/(_service)/(_components)/page-transformer-components/page-html-transformer.tsx
3-@/app/@right/(_service)/(_components)/page-transformer-components/wrappers/full-screen-wrapper.tsx
4-app/@right/(_service)/(_components)/page-transformer-components/wrappers/wrapper.tsx
5-app/@right/(_service)/(_types)/page-wrapper-types.ts
6-// @/app/@right/public/(_servise)/(_config)/public-pages-config.ts
7-//@app/@right/public/(_servise)/(_libs)/utils.ts
8-@/app/@right/(_service)/(_components)/page-transformer-components/header-sections-components/header-section.tsx
9-@app/@right/(_service)/(_components)/page-transformer-components/header-sections-components/page-header-elements.tsx
10-@app/@right/(_service)/(_components)/page-transformer-components/header-sections-components/announcement.tsx
11-@app/@right/(_service)/(_components)/page-transformer-components/footer-sections-components/footer-section.tsx
12-@app/@right/(_service)/(_components)/page-transformer-components/body-sections-components/body-section.tsx
2.1. Базовый набор компонентов: рендер шаблонных страниц «из коробки»
Назначение
Стандартные компоненты представляют собой базовый набор элементов для создания типизированных страниц в едином стиле. Эта коллекция включает в себя системообразующие элементы архитектуры: динамический роутинг, центральный трансформер, обёртки для разных типов контента, унифицированные секции заголовков и футеров, а также конфигурационные файлы и типизацию.
Ключевые принципы стандартных компонентов:
Единообразие интерфейса — все компоненты следуют общим принципам дизайна и поведения
AI-совместимость — структура оптимизирована для автоматической генерации контента
SEO-оптимизация — встроенная поддержка поисковой оптимизации и правильной HTML-семантики
Типизированная архитектура — строгая типизация через TypeScript для предсказуемого поведения
Модульность — каждый компонент решает конкретную задачу и может использоваться независимо
Эти компоненты формируют основу системы, которая гарантирует визуальную консистентность и техническое единообразие всех AI-генерируемых страниц.
2.1.1. Компонент [[...slug]]/page.tsx: универсальный catch-all route для динамических URL
2.1.1.1. Почему Next.js использует catch-all routes: гибкость, скорость деплоя
Жадный маршрут (catch-all route) — это тип динамического роута в Next.js, который позволяет обрабатывать неограниченное количество вложенных сегментов URL с помощью одной единственной страницы-компонента. В конструкции [...slug] или [[...slug]] (опциональный) массив slug в параметрах будет содержать все части пути, независимо от их количества.
Почему это круто:
Произвольное количество уровней вложенности.
Вам не нужно создавать отдельные папки и файлы в структуре проекта под каждый новый путь. Например, путь /public/category/nike/sneakers/black/12345 обработается ровно тем же компонентом, что и /public/test.Гибкая навигация и быстрый запуск новых страниц.
Достаточно добавить соответствующий конфиг — и страница мгновенно появляется в приложении, без необходимости заводить новые компоненты или поддерживать сложную файловую структуру.Автоматизация:
Такое построение идеально сочетается с auto-generated sidebar/menu: создаём нужный пункт в навигации и "умный" роутинг автоматом отрабатывает отображение нужной страницы.
2.1.1.2. Значение динамических URL для AI-ассистентов: бесконечное расширение контента
В современных SaaS/AI-продуктах или headless сайтах зачастую требуется быстро создавать новые страницы "на лету" по запросу юзера или администратора — страница конструируется и конфигурируется не руками, а через генерацию (AI или админ-панель).
Архитектура с жадным роутом:
Снимает ограничения структуры и глубины вложенности страниц,
Позволяет работать с любым количеством уровней иерархии (например, меню ресторанов с многоуровневой категоризацией; карточки товаров по структуре каталог/категория/производитель/линейка/артикул),
Обеспечивает свободу для пользователей и разработчиков — навигация строится только по логике проекта, а не привязана к файловой структуре.
2.1.1.3. Примеры использования:
QR-меню для ресторана:
/public/menu/bar/beer и /public/menu/hot-dishes/italian/pasta → любые глубины подкатегорий.Интернет-магазин:
/public/shop/clothes/mens/jackets/leather/2023/black/style123Образовательный портал:
/public/tutorials/python/basics/loops/while-loop/example1
2.1.1.4. Как работает компонент page.tsx
Основные задачи:
Приходит массив slug из URL (например, ["category", "nike", "sneakers"]).
Функция generateStaticParams собирает все маршруты, которые присутствуют в вашем конфиге (pages-config.ts), и передаёт Next.js их для статической генерации.
Функция getPageBySlug быстро находит в массиве конфигов нужную страницу ровно по тому slug, который пришёл (обычно с добавлением "public" в начало).
export async function generateStaticParams() {
const pages: PageConfig[] = getAllPublicPages();
return pages.map((page: PageConfig) => ({
slug: page.metadata.slug ? page.metadata.slug.slice(1) : [],
}));
}
Здесь мы формируем все возможные пути для статической генерации: если в конфиге есть ["public","cat","subcat","product"], то будет сгенерирован путь /public/cat/subcat/product.
Далее, когда происходит рендеринг:
export default async function PublicDynamicSlugPage({ params }: Props) {
const resolvedParams = await params;
const slugArr = resolvedParams.slug ?? [];
const pageConfig = getPageBySlug(["public", ...slugArr]);
if (!pageConfig) {
return <div>Page not found</div>;
}
return <PageHtmlTransformer data={pageConfig} />;
}
slugArr может быть любой длины!
"public" добавляется для корректного поиска в массиве.
Функция поиска работает по полному совпадению slug, что исключает коллизии.
h5 2.1.1.5. Кратко о ключевых функциях:
generateStaticParams — сообщает Next.js ВСЕ существующие вложенные пути для генерации.
getPageBySlug — ищет нужную страницу в массиве по полному match slug-массива.
PageHtmlTransformer — универсальный отрисовщик, которому неважно, какой глубины вложенности страница: он строит компонент на лету из твоего формализованного конфига.
2.1.1.6. Заключение. Почему выбран именно такой подход
Гибкость для новых разделов: не надо думать о папках — просто добавь конфиг, и страница появится!
Удобство редактирования и масштабирования навигации: весь роутинг и навигация управляются на уровне данных (массивы slugs), а не файлов.
Сценарии реального бизнеса: автоматизация генерации меню, товаров, курсов, документации, блогов и всего, что только может прийти в голову.
Унификация для AI-ассистентов: такой подход идеально сочетается с автоматической генерацией контента по текстовому описанию (prompt) — любые новые страницы появляются мгновенно и не ограничены глубиной маршрута.
2.1.1.7. Компонент
// @/app/@right/public/(_routing)/[[...slug]]/page.tsx
import { PageHtmlTransformer } from "@/app/@right/(_service)/(_components)/page-transformer-components/page-html-transformer";
import { getPageBySlug } from "@/app/@right/(_service)/(_config)/pages-config";
import { constructMetadata } from "@/lib/construct-metadata";
import type { PageConfig } from "@/app/@right/(_service)/(_types)/page-wrapper-types";
import { getAllPublicPages } from "../../(_servise)/(_libs)/get-all-public-pages";
interface Props {
params: Promise<{ slug?: string[] }>;
}
export async function generateStaticParams() {
const pages: PageConfig[] = getAllPublicPages();
return pages.map((page: PageConfig) => ({
slug: page.metadata.slug || [],
}));
}
export async function generateMetadata({ params }: Props) {
const resolvedParams = await params;
const slugArr = resolvedParams.slug ?? [];
const pageConfig = getPageBySlug(["public", ...slugArr]);
if (!pageConfig) return {};
return constructMetadata(pageConfig.metadata);
}
export default async function PublicDynamicSlugPage({ params }: Props) {
const resolvedParams = await params;
const slugArr = resolvedParams.slug ?? [];
const publicPageConfig = getPageBySlug(["public", ...slugArr]);
if (!publicPageConfig) {
return <div>Page not found</div>;
}
return <PageHtmlTransformer data={publicPageConfig} />;
}
2.1.1.7. TL;DR по catch-all: когда и как использовать в AI-проекте
Жадные маршруты и централизованный конфиг страниц делают архитектуру твоего приложения максимально гибкой, расширяемой и дружественной как для разработчиков, так и для AI-оркестрации контента. Новая страница = новая строка в конфиге, никаких новых файлов или папок — навигация и рендер подстраиваются автоматически!
2.1.2. Реальные кейсы catch-all route: магазин, меню ресторана, образовательный портал
2.1.2.1. Архитектурная роль и назначение компонента
PageHtmlTransformer является ядром всей системы рендеринга — это универсальный "дирижёр", который получает декларативное описание страницы (PageConfig) и превращает его в живые React-компоненты. Его главная задача — обеспечить единообразие визуального представления при максимальной гибкости содержимого.
h6 Ключевые функции:
Централизованная логика рендеринга — один компонент управляет всем процессом отображения секций
Типизированная обработка — каждый тип секции получает соответствующую обёртку и рендеринг
Управление темами и адаптивностью — центральная точка для проброса контекстных данных
Унификация стилей — гарантирует единообразие header/footer секций across всех страниц
2.1.2.2. Принципиальное решение: почему header и footer вынесены отдельно
Решение вынести HeaderSection и FooterSection за рамки дефолтной логики BodySection продиктовано фундаментальными требованиями к единообразию дизайна:
h6 Проблема с AI-генераторами контента:
Автоматизированные ИИ-генераторы часто создают непоследовательный контент в части оформления заголовков и заключений
Каждая секция может иметь уникальное body-содержание, но начало и конец должны следовать строгим стандартам бренда
Header и Footer — это "якоря стиля", которые задают тон всей секции и обеспечивают визуальную связность
Архитектурные преимущества:
Гарантированное единообразие — независимо от того, как AI сгенерирует body-контент, header и footer всегда будут выдержаны в корпоративном стиле
Централизованный контроль — изменения в стилях заголовков применяются мгновенно ко всем страницам
Предсказуемая структура — разработчики и дизайнеры могут рассчитывать на стабильные паттерны
2.1.2.3. Управление темами и контекстом
export function PageHtmlTransformer({ data }: PageHtmlTransformerProps) {
const { theme } = useTheme();
// ... rest of logic
}
Зачем пробрасывать тему именно отсюда:
Сценарии, требующие theme-aware логики:
Условные изображения: В светлой теме используется одна картинка, в темной — другая
Динамические стили: Компоненты с особой логикой цветовых схем
Анимации и переходы: Разные эффекты для light/dark режимов
Аналогично с мобильной версией (isMobile):
Условный рендеринг: Некоторые компоненты полностью отключаются на мобильных
Кардинально разная вёрстка: Когда Tailwind CSS недостаточно гибкий
Производительность: Отказ от тяжёлых элементов на слабых устройствах
Пример использования:
// В дочернем компоненте
{theme === 'dark' ? <DarkModeImage /> : <LightModeImage />}
{!isMobile && <DesktopOnlyFeature />}
2.1.2.4. Два типа Wrapper: зачем нужна дифференциация
FullScreenWrapper vs Wrapper — принципиальные различия:
FullScreenWrapper (для hero-section и подобных):
Полноэкранная компоновка — занимает всю высоту viewport
Поддержка фоновых медиа — video/image backgrounds с наложением контента
Z-index управление — сложная слоистая структура для overlay-эффектов
Позиционирование — absolute/relative позиционирование для центрирования контента
Wrapper (стандартный):
Секционная вёрстка — обычные блоки с padding/margin
Container-based — стандартная сетка и отступы
Простота и производительность — минимальные CSS-свойства
Почему нельзя объединить в один компонент:
Конфликты CSS — fullscreen и container логики несовместимы
Производительность — лишние стили влияют на рендеринг
Семантическая ясность — разные задачи требуют разных подходов
Maintenance — проще поддерживать два специализированных компонента
2.1.2.5. Ограничения renderSectionContent и кастомные случаи
Проблемы со стандартной функцией renderSectionContent:
function renderSectionContent(config: any) {
return (
<>
{config.headerContent && <HeaderSection headerContent={config.headerContent} />}
{config.bodyContent && <BodySection type={config.type}>{config.bodyContent.content}</BodySection>}
{config.footerContent && <FooterSection actions={config.footerContent.actions} />}
</>
);
}
Ограничения этого подхода:
Отсутствие state management — нет возможности использовать useState, useEffect
Статичность данных — контент передается как готовые данные, без интерактивности
Отсутствие event handling — невозможно обрабатывать пользовательские события
Нет lifecycle методов — компоненты не могут реагировать на mount/unmount
Когда нужны кастомные случаи:
Интерактивные формы — компоненты с валидацией и отправкой данных
Анимированные секции — сложные transitions и animations
Real-time данные — компоненты с подпиской на WebSocket или API
Условная логика — сложные вычисления на основе пользовательского ввода
Решение через кастомные кейсы:
switch (section.type) {
case "interactive-form-section": {
return <CustomInteractiveForm key={config.id} {...config} />;
}
case "real-time-dashboard": {
return <CustomDashboard key={config.id} {...config} theme={theme} />;
}
// стандартные случаи...
default: {
return (
<Wrapper key={config.id}>
{renderSectionContent(config)}
</Wrapper>
);
}
}
2.1.2.6. Анализ архитектурной роли и рабочего процесса
PageHtmlTransformer выполняет несколько ключевых архитектурных ролей:
Фасад паттерн — предоставляет простой интерфейс для сложной системы рендеринга
Factory паттерн — создает нужные компоненты на основе типа секции
Strategy паттерн — выбирает стратегию рендеринга (Wrapper/FullScreenWrapper) в зависимости от контекста
Template Method паттерн — определяет скелет алгоритма рендеринга с возможностью переопределения отдельных шагов
Рабочий процесс:
Получение конфигурации — принимает PageConfig с массивом секций
Инициализация контекста — получает theme, определяет isMobile
Итерация по секциям — для каждой секции определяет тип и способ рендеринга
Выбор обёртки — FullScreenWrapper для hero-секций, Wrapper для остальных
Рендеринг контента — применяет renderSectionContent или кастомный компонент
Сборка финальной страницы — объединяет все секции в единую структуру
2.1.2.7. Компонент
// @/app/@right/(_service)/(_components)/page-transformer-components/page-html-transformer.tsx
"use client";
import {
PageConfig,
Section,
SectionConfig,
} from "../../(_types)/page-wrapper-types";
import { BodySection } from "./body-sections-components/body-section";
import { FooterSection } from "./footer-sections-components/footer-section";
import { HeaderSection } from "./header-sections-components/header-section";
import { useTheme } from "next-themes";
import { FullScreenWrapper } from "./wrappers/full-screen-wrapper";
import { Wrapper } from "./wrappers/wrapper";
interface PageHtmlTransformerProps {
data: PageConfig;
}
function renderSectionContent(config: any) {
return (
<>
{config.headerContent && (
<HeaderSection headerContent={config.headerContent} />
)}
{config.bodyContent && (
<BodySection type={config.type}>
{config.bodyContent.content}
</BodySection>
)}
{config.footerContent && (
<FooterSection actions={config.footerContent.actions} />
)}
</>
);
}
export function PageHtmlTransformer({ data }: PageHtmlTransformerProps) {
const { theme } = useTheme();
if (!data?.sections?.length) return null;
return (
<>
{data.sections.map((section: Section, idx: number) => {
switch (section.type) {
case "hero-section": {
const config = section as SectionConfig;
return (
<FullScreenWrapper
key={config.id || idx}
videoUrl={config.videoUrl}
imageUrl={config.imageUrl}
className={config.sectionClassName}
>
{renderSectionContent(config)}
</FullScreenWrapper>
);
}
// ...другие case без изменений
default: {
const config = section as any;
return (
<Wrapper
key={config.id || idx}
className={config.sectionClassName}
>
{renderSectionContent(config)}
</Wrapper>
);
}
}
})}
</>
);
}
2.1.2.7.Заключение
PageHtmlTransformer — это сердце системы, которое превращает декларативные описания в живые, интерактивные и стилистически единообразные веб-страницы. Его архитектура балансирует между гибкостью (поддержка кастомных компонентов) и последовательностью (стандартизированные header/footer), что делает его идеальным инструментом для AI-управляемой генерации контента.
2.1.3. FullScreenWrapper vs Wrapper: когда использовать полноэкранную оболочку
2.1.3.1. Основное назначение
FullScreenWrapper предназначен для секций, требующих полноэкранного отображения с поддержкой видео/изображений на фоне. Это базовый контейнер для hero-секций, где контент накладывается поверх медиа-элементов.
2.1.3.2. Слоистая структура
<section className="relative flex min-h-screen flex-col py-10 lg:py-14 bg-background">
{backgroundElement} // z-0
<div className="relative z-10 flex flex-col flex-1">
{children} // Header → Body → Footer
</div>
</section>
Технические особенности:
min-h-screen — гарантирует заполнение всего viewport
relative позиционирование для z-index управления
Background элементы (видео/изображение) с absolute и z-0
Контент с relative z-10 всегда поверх фона
2.1.3.3. Кастомизация и расширения
Место для градиентов и анимаций: Добавляйте эффекты через className prop:
<FullScreenWrapper className="bg-gradient-to-br from-blue-900 to-purple-900">
Контроль прозрачности фона: Видео автоматически получает opacity-40 для читаемости текста, но это легко переопределить.
Анимации переходов: transition-all duration-500 уже встроен для плавных изменений фона.
2.1.3.4. Применение для кастомных шаблонов
Если нужны разделители между секциями, бордюры, подсветка или управление размытием — FullScreenWrapper идеальное место для реализации:
// Пример с бордюром и градиентом
<FullScreenWrapper className="border-t-4 border-gradient bg-blur-effect">
2.1.3.5. Компонент
// @/app/@right/(_service)/(_components)/page-transformer-components/wrappers/full-screen-wrapper.tsx
import React, { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";
interface FullScreenWrapperProps extends HTMLAttributes<HTMLDivElement> {
videoUrl?: string;
imageUrl?: string;
className?: string;
children: React.ReactNode;
}
export function FullScreenWrapper({
videoUrl,
imageUrl,
className,
children,
...props
}: FullScreenWrapperProps) {
let backgroundElement: React.ReactNode = null;
if (videoUrl) {
backgroundElement = (
<video
className="absolute inset-0 size-full object-cover z-0 opacity-40 transition-all duration-500"
autoPlay
loop
muted
playsInline
src={videoUrl}
/>
);
} else if (imageUrl) {
backgroundElement = (
<img
className="absolute inset-0 size-full object-cover z-0"
src={imageUrl || "/placeholder.svg"}
alt="Background"
/>
);
}
return (
<section
className={cn(
"relative flex min-h-screen flex-col py-10 lg:py-14 bg-background",
className
)}
{...props}
>
{backgroundElement}
<div className="relative z-10 flex flex-col flex-1">{children}</div>
</section>
);
}
2.1.4. Wrapper: базовая секционная оболочка с container mx-auto
2.1.4.1. Назначение
Wrapper — базовая обёртка для большинства контентных секций. Обеспечивает стандартные отступы, центрирование контента и контейнеризацию.
2.1.4.2. Структура
<section className="py-10 lg:py-14 bg-background">
<div className="container mx-auto px-4">
{children} // Header → Body → Footer
</div>
</section>
Ключевые параметры:
py-10 lg:py-14 — адаптивные вертикальные отступы
container mx-auto px-4 — центрированный контент с горизонтальными отступами
bg-background — использует CSS-переменную темы
2.1.4.3. Точки кастомизации
Фоновые эффекты:
Легко добавлять через className:
<Wrapper className="bg-gradient-to-r from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
Разделители между секциями:
<Wrapper className="border-t border-border/50">
Анимации появления:
<Wrapper className="animate-fade-in transition-all duration-700">
Управление размытием и подсветкой:
<Wrapper className="backdrop-blur-sm shadow-inner">
2.1.4.4. Средне-кастомные переиспользуемые шаблоны
Когда нужен шаблон с уникальной стилизацией, но стандартной структурой — Wrapper ваш выбор:
// Пример для pricing-секций
<Wrapper className="bg-gradient-to-br from-green-50 to-green-100 border-2 border-green-200">
<HeaderSection />
<PricingTable />
<FooterSection />
</Wrapper>
2.1.3.5. Компонент
// app/@right/(_service)/(_components)/page-transformer-components/wrappers/wrapper.tsx
import React, { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";
interface WrapperProps extends HTMLAttributes<HTMLDivElement> {
className?: string;
children: React.ReactNode;
}
export function Wrapper({ className, children, ...props }: WrapperProps) {
return (
<section
className={cn("py-10 lg:py-14 bg-background", className)}
{...props}
>
<div className="container mx-auto px-4">{children}</div>
</section>
);
}
Зачем два отдельных wrapper'а?
Технические причины:
FullScreenWrapper содержит сложную логику медиа-фонов и z-index управления
Wrapper оптимизирован для производительности — минимум CSS-правил
Разные подходы к позиционированию: relative vs static
Несовместимые CSS-свойства: min-h-screen vs py-*
Практические выгоды:
Четкое разделение ответственности
Легкость кастомизации под конкретные задачи
Предсказуемое поведение для AI-генерации
Отсутствие CSS-конфликтов между типами секций
Для AI-систем: Простое правило выбора — immersive контент (hero, showcases) = FullScreenWrapper, информационный контент = Wrapper.
2.1.5. page-wrapper-types.ts: TypeScript-контракт между ИИ и UI
2.1.5.1. Роль типизации: контракт между AI и рендером.
Файл page-wrapper-types.ts — это формальный контракт между AI-генератором и системой рендеринга. Здесь определяется архитектура всех возможных компонентов страницы и правила их взаимодействия.
2.1.5.2. Категории SectionType и AI-генерация.
Принцип работы AI: Искусственный интеллект сначала анализирует доступные типы секций из enum SectionType, затем принимает решение о том, какие из них добавить к конкретной странице на основе пользовательского запроса.
Ожидается, что для каждого из 25+ типов секций в базе знаний AI будут загружены исчерпывающие инструкции и примеры, достаточные для автономной генерации контента.
export type SectionType =
| "hero-section" | "cta-section" | "faq-section"
| "features-section" | "testimonials-section" | "pricing-section"
// ... и другие типы
2.1.5.3. SEO-оптимизация и метаданные.
PageMetadata обеспечивает корректное формирование мета-тегов для поисковых систем:
export interface PageMetadata {
id: string; // Уникальный идентификатор
title: string; // <title> тег для SEO
description: string; // <meta description> для сниппетов
image?: string; // Open Graph изображение
slug?: string[]; // URL-структура страницы
type: SectionType; // Тип основной секции
}
Автоматическая генерация мета-тегов происходит в generateMetadata() функции, которая извлекает данные из конфига и передает их в constructMetadata() для формирования корректных HTML-заголовков.
2.1.5.4. HeaderContentConfig: контроль SEO-иерархии.
Критически важно для SEO:
interface HeaderContentConfig {
heading: string;
headingLevel?: 1 | 2; // H1/H2 для поисковой иерархии
description?: string;
showBorder?: boolean;
}
Осторожно с заголовками: Header-секция опциональна, поскольку в некоторых типах секций заголовки H1/H2 могут размещаться внутри bodyContent. Это потенциально опасно для единообразия интерфейса — основной цели данного туториала.
2.1.5.5. BodySection: максимальная свобода дизайна.
bodyContent?: React.ReactNode;
Архитектурное решение: Тело секции получает полную свободу для реализации любого дизайна, в то время как Header и Footer жестко стандартизированы для поддержания единого стиля.
interface FooterContentConfig {
actions?: {
label: string;
href: string;
variant?: "default" | "secondary" | "outline" | "ghost" | "link";
}[];
}
2.1.5.6. FooterContentConfig: минимализм по дизайну.
Текущая реализация предполагает только опциональные кнопки действий. Никаких дополнительных элементов — максимальная простота и единообразие.
2.1.5.7. Принципы расширения типизации.
Для добавления новых SectionType:
AI получает новые инструкции в базу знаний
Добавляется тип в enum SectionType
При необходимости расширяется интерфейс конфигурации
Баланс стандартизации: Header и Footer строго типизированы для SEO и единообразия, Body получает максимальную гибкость для творческих решений AI-генераторов
2.1.5.8. Компонент
2.1.5.9. customComponentsAnyTypeData: поддержка произвольных кастомных компонентов
Поле customComponentsAnyTypeData?: any; в SectionConfig предназначено для передачи данных в секции, реализованные через сложные или уникальные кастомные компоненты, не вписывающиеся в стандартные интерфейсы.
Тип и структура этих данных должны быть подробно и строго описаны внутри самого кастомного компонента. Это решение обеспечивает гибкость для интеграции интерактивных, динамических и продвинутых пользовательских секций без нарушения общего контракта типизации основной архитектуры страниц.
// app/@right/(_service)/(_types)/page-wrapper-types.ts
export interface MetadataConfig {
title?: string;
description?: string;
}
export interface PageMetadata {
id: string;
title: string;
description: string;
image?: string;
slug?: string[];
type: SectionType;
}
export type SectionType =
| "hero-section"
| "cta-section"
| "faq-section"
| "features-section"
| "testimonials-section"
| "pricing-section"
| "contact-section"
| "blog-posts-section"
| "product-grid-section"
| "image-gallery-section"
| "text-block-section"
| "video-section"
| "team-section"
| "about-us-section"
| "newsletter-section"
| "social-proof-section"
| "comparison-table-section"
| "map-section"
| "custom-html-section"
| "changelog-section"
| "comparison-two-column-section"
| "comparison-three-column-section"
| "feature-showcase-section";
export interface BaseSection {
id: string;
type: SectionType;
className?: string;
}
export interface HeaderContentConfig {
announcement?: {
badgeText?: string;
descriptionText?: string;
href?: string;
};
heading: string;
headingLevel?: 1 | 2;
description?: string;
showBorder?: boolean;
}
export interface FooterContentConfig {
actions?: {
label: string;
href: string;
variant?:
| "default"
| "secondary"
| "destructive"
| "outline"
| "ghost"
| "link";
}[];
}
export interface SectionConfig extends BaseSection {
type: SectionType;
headerContent: HeaderContentConfig;
bodyContent?: React.ReactNode;
footerContent?: FooterContentConfig;
videoUrl?: string;
imageUrl?: string;
sectionClassName?: string;
contentWrapperClassName?: string;
customComponentsAnyTypeData?: any;
}
export type Section = SectionConfig;
export interface PageConfig {
metadata: PageMetadata;
sections: Section[];
}
export type SlugType = string[];
2.1.6. public-pages-config.ts: реестр публичных страниц и Slug-маршрутов
2.1.6.1. Роль конфига в архитектуре системы
Файл public-pages-config.ts является результатом работы искусственного интеллекта — автоматически генерируемым реестром всех публичных страниц приложения. Этот конфиг создается ИИ в автоматическом режиме на основе системных инструкций и пользовательского запроса, напрямую зависит от доступных компонентов в примерах базы знаний и перечислений в типизации SectionType, которые мы рассматривали выше. Сгенерированная структура служит мостом между AI-анализом требований пользователя и системой рендеринга страниц.
2.1.6.2. Когда использовать файловый конфиг
Оптимальные сценарии для конфиг-файла:
Проекты с не более 10-15 страниц
Статичный контент, который редко изменяется
Прототипирование и MVP — быстрый старт без базы данных
Landing pages с фиксированной структурой
Когда переходить на базу данных:
В production-версии на крупном проекте файловый подход имеет смысл только при небольшом количестве страниц. Большинство случаев требуют хранения этой информации в базе данных для динамического управления контентом.
2.1.6.3. Структура конфига
export const PublicPagesConfig = {
pages: [
{
metadata: {
id: "public",
title: "Enterprise-Grade AI Next.js starter",
description: "Free Open-Source starter kit...",
slug: ["public", "test"],
type: "hero-section",
},
sections: [
{
id: "test-block",
type: "hero-section",
headerContent: { /* SEO-заголовки */ },
bodyContent: {},
footerContent: { /* Кнопки действий */ },
videoUrl: "/_static/video/ai-loop.mp4",
contentWrapperClassName: "text-white",
} as SectionConfig,
],
},
] as PageConfig[],
};
2.1.6.4. Принцип категоризации конфигов
Важное архитектурное решение: Размещение конфига в директории public/(_service)/(_config)/ указывает на его принадлежность к публичным страницам.
Масштабирование по категориям:
public-pages-config.ts — публичные страницы (landing, about, contact)
docs-pages-config.ts — документация с расширенной навигацией
admin-pages-config.ts — административные панели
blog-pages-config.ts — блоговые записи и статьи
Каждая категория получает:
Собственный файл конфигурации
Специализированные типы секций
Уникальную логику рендеринга
2.1.6.5. Взаимодействие с AI-генерацией
Для AI-систем конфиг служит:
Шаблоном структуры — как должны выглядеть PageConfig объекты
Примером данных — референсные значения для генерации
Валидацией типов — строгая типизация через TypeScript
Рабочий процесс:
AI анализирует существующий конфиг
Генерирует новый PageConfig на основе промпта
Разработчик добавляет конфиг в массив pages
Система автоматически подхватывает новые маршруты
2.1.6.6. Преимущества и ограничения
Преимущества файлового подхода:
Версионирование через Git
Type safety на этапе компиляции
Нулевая латентность — нет запросов к БД
Простота деплоя — статическая генерация
Ограничения:
Отсутствие динамического управления
Необходимость перебилда при изменениях
Неподходящ для user-generated content
Сложность масштабирования свыше 20-30 страниц
2.1.6.7. Компонент
// @/app/@right/public/(_servise)/(_config)/public-pages-config.ts
import {
PageConfig,
SectionConfig,
} from "@/app/@right/(_service)/(_types)/page-wrapper-types";
export const PublicPagesConfig = {
pages: [
{
metadata: {
id: "public",
title: "Enterprise-Grade AI Next.js starter",
description: "Free Open-Source starter kit...",
slug: ["public", "test"],
type: "hero-section",
},
sections: [
{
id: "test-block",
type: "hero-section",
headerContent: {
announcement: {
badgeText: "Thanks",
descriptionText: "AI-SDK V5 & Vercel AI",
href: "https://github.com/aifa-agi/aifa",
},
heading: "Enterprise-Grade AI Next.js starter",
description:
"Free Open-Source starter kit to build, deploy, and scale intelligent AI applications. Artifacts Feature, features secure multi-provider auth, Stripe payments, vector knowledge bases, deep-research agents, and a unique fractal architecture designed for the future of AI.",
showBorder: false,
headingLevel: 1,
},
bodyContent: {},
footerContent: {
actions: [
{
label: "Get Started",
href: "/https://github.com/aifa-agi/aifa",
variant: "default",
},
{ label: "Browse Docs", href: "/docs", variant: "ghost" },
],
},
videoUrl: "/_static/video/ai-loop.mp4",
contentWrapperClassName: "text-white",
} as SectionConfig,
],
},
] as PageConfig[],
};
2.1.6.7. Заключение
public-pages-config.ts представляет собой практичное решение для небольших и средних проектов, обеспечивающее баланс между простотой управления и функциональностью. Для крупных проектов этот подход служит отличной отправной точкой перед миграцией на database-driven архитектуру.
2.1.7. utils.ts: вспомогательные функции конфигурации
Утилиты для работы с конфигами страниц. Содержит две ключевые функции: getAllPublicPages() — фильтрует все страницы с префиксом "public" для статической генерации, и getPageBySlug() — находит конкретную страницу по полному совпадению slug-массива с учетом регистра.
// @app/@right/public/(_servise)/(_libs)/utils.ts
import {
PageConfig,
SlugType,
} from "@/app/@right/(_service)/(_types)/page-wrapper-types";
import { PublicPagesConfig } from "../(_config)/public-pages-config";
export function getAllPublicPages(): PageConfig[] {
return PublicPagesConfig.pages.filter(
(page: PageConfig) => page.metadata.slug?.[0] === "public"
);
}
export function getPageBySlug(slug: SlugType): PageConfig | undefined {
return PublicPagesConfig.pages.find(
(page: PageConfig) =>
JSON.stringify(
page.metadata.slug?.map((s: string) => s.toLowerCase())
) === JSON.stringify(slug.map((s: string) => s.toLowerCase()))
);
}
2.1.8. header-section.tsx: унифицированный заголовок секций
Стандартизированный компонент заголовка для всех типов секций. Обеспечивает единообразие через опциональные элементы: анонс, заголовок H1/H2, описание. Ключевая роль в SEO-оптимизации и визуальной консистентности AI-генерируемых страниц.
// @/app/@right/(_service)/(_components)/page-transformer-components/header-sections-components/header-section.tsx
import React from "react";
import { cn } from "@/lib/utils";
import {
PageHeaderDescription,
PageHeaderHeading,
} from "./page-header-elements";
import { Announcement } from "./announcement";
import { HeaderContentConfig } from "../../../(_types)/page-wrapper-types";
export type HeaderSectionProps = {
headerContent: HeaderContentConfig;
} & React.HTMLAttributes<HTMLDivElement>;
export function HeaderSection({
headerContent,
className,
...props
}: HeaderSectionProps) {
if (!headerContent) return null;
const {
announcement,
heading,
headingLevel = 1,
description,
showBorder = false,
} = headerContent;
return (
<section
className={cn(
showBorder && "border-t-4 border-b-4 border-primary",
className
)}
{...props}
>
<div className="container mx-auto px-4">
<div className="flex flex-col items-center gap-1 py-8 md:py-10 lg:py-12">
{announcement && (
<Announcement
badgeText={announcement.badgeText}
descriptionText={announcement.descriptionText}
href={announcement.href}
/>
)}
<PageHeaderHeading level={headingLevel}>{heading}</PageHeaderHeading>
{description && (
<PageHeaderDescription>{description}</PageHeaderDescription>
)}
</div>
</div>
</section>
);
}
2.1.9. header-section.tsx: унифицированный заголовок секций
Атомарные компоненты для построения заголовков: PageHeaderHeading с поддержкой H1/H2, PageHeaderDescription для подзаголовков, PageActions для размещения кнопок. Используют createElement для динамической генерации HTML-тегов с правильной SEO-иерархией.
// @app/@right/(_service)/(_components)/page-transformer-components/header-sections-components/page-header-elements.tsx
import { HTMLAttributes, createElement } from "react";
import { cn } from "@/lib/utils";
type HeadingTag = "h1" | "h2";
interface PageHeaderHeadingProps extends HTMLAttributes<HTMLHeadingElement> {
level?: 1 | 2;
}
function PageHeaderHeading({
className,
level = 1,
...props
}: PageHeaderHeadingProps) {
// Выбор тега по уровню
const Heading: HeadingTag = level === 1 ? "h1" : "h2";
const h1Classes = "text-2xl sm:text-3xl md:text-6xl lg:text-7xl";
const h2Classes = "text-lg sm:text-xl md:text-3xl lg:text-4xl";
// Правильное использование createElement
return createElement(Heading, {
className: cn(
"text-center font-bold leading-tight tracking-tighter font-serif",
level === 1 ? h1Classes : h2Classes,
className
),
...props,
});
}
function PageHeaderDescription({
className,
...props
}: HTMLAttributes<HTMLParagraphElement>) {
return (
<p
className={cn(
"max-w-2xl text-balance text-center text-base font-light text-muted-foreground sm:text-lg",
className
)}
{...props}
/>
);
}
function PageActions({ className, ...props }: HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn(
"flex w-full items-center justify-center gap-2 pt-2",
className
)}
{...props}
/>
);
}
// Экспорт компонентов
export { PageActions, PageHeaderDescription, PageHeaderHeading };
2.1.10. announcement.tsx: компонент анонсов
Интерактивный бейдж для анонсов и уведомлений. Поддерживает навигацию, клавиатурное управление и условный рендеринг. Автоматически скрывается при отсутствии контента. Используется для привлечения внимания к важным обновлениям или ссылкам.
// @app/@right/(_service)/(_components)/page-transformer-components/header-sections-components/announcement.tsx
"use client";
import { useRouter } from "next/navigation";
import { Badge } from "@/components/ui/badge";
import { ArrowRight } from "lucide-react";
import { cn } from "@/lib/utils";
interface AnnouncementProps {
badgeText?: string; // Сделали необязательным
descriptionText?: string; // Сделали необязательным
href?: string; // Сделали необязательным
className?: string;
}
export function Announcement({
badgeText,
descriptionText,
href,
className,
}: AnnouncementProps) {
const router = useRouter();
const handleOnClick = () => {
if (href) {
router.push(href);
}
};
// Если нет текста для бейджа, описания или ссылки, возвращаем null
if (!badgeText && !descriptionText && !href) {
return null;
}
return (
<div
className={cn(
"flex cursor-pointer items-center gap-2 rounded-full border border-primary bg-muted px-3 py-1 text-sm transition-colors hover:bg-muted/80",
className
)}
onClick={handleOnClick}
role="link"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
handleOnClick();
}
}}
>
{badgeText && (
<Badge variant="secondary" className="text-xs">
{badgeText}
</Badge>
)}
{descriptionText && (
<span className="text-muted-foreground">{descriptionText}</span>
)}
{href && <ArrowRight className=" h-3 w-3 text-muted-foreground" />}
</div>
);
}
2.1.11. footer-section.tsx: унифицированные действия секций
Стандартизированный футер с кнопками действий. Поддерживает множественные кнопки с разными вариантами стилизации. Обеспечивает единообразие CTA-элементов across всех секций. Автоматически скрывается при отсутствии действий.
// @app/@right/(_service)/(_components)/page-transformer-components/footer-sections-components/footer-section.tsx
"use client";
import { useRouter } from "next/navigation";
import type { HTMLAttributes } from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { PageActions } from "../header-sections-components/page-header-elements";
interface FooterAction {
label: string;
href: string;
variant?:
| "default"
| "secondary"
| "destructive"
| "outline"
| "ghost"
| "link";
}
interface FooterSectionProps extends HTMLAttributes<HTMLDivElement> {
actions?: FooterAction[];
}
export function FooterSection({
actions,
className,
...props
}: FooterSectionProps) {
const router = useRouter();
if (!actions || actions.length === 0) {
return null;
}
return (
<section className={cn("py-4 md:py-6 lg:py-8", className)} {...props}>
<div className="container mx-auto px-4">
<PageActions>
{actions.map((action) => (
<Button
key={action.href} // href должен быть уникальным!
size="sm"
variant={action.variant || "default"}
onClick={() => router.push(action.href)}
>
{action.label}
</Button>
))}
</PageActions>
</div>
</section>
);
}
2.1.12. body-section.tsx: контейнер для произвольного контента
2.1.12.1. Назначение и философия
BodySection — это максимально гибкий контейнер для основного содержимого секций. В отличие от строго стандартизированных Header и Footer, этот компонент предоставляет полную свободу для размещения любого React-контента.
2.1.12.2. Принципы генерации контента
Критически важно: Контент для bodyContent должен генерироваться как обычный TSX без элементов .map(), где все элементы представлены в развернутом виде:
// ✅ Правильно - развернутый TSX
bodyContent: (
<>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="feature-card">
<h3>Feature 1</h3>
<p>Description 1</p>
</div>
<div className="feature-card">
<h3>Feature 2</h3>
<p>Description 2</p>
</div>
<div className="feature-card">
<h3>Feature 3</h3>
<p>Description 3</p>
</div>
</div>
</>
)
```
```
// ❌ Неправильно - использование .map()
bodyContent: (
<div className="grid">
{features.map(feature => <FeatureCard key={feature.id} {...feature} />)}
</div>
)
2.1.12.3. Когда использовать кастомные компоненты
Если секция требует сложной логики (состояние, эффекты, интерактивность), создавайте самостоятельный компонент и добавляйте его в PageHtmlTransformer как отдельный
case:
switch (section.type) {
case "interactive-pricing-section": {
return <CustomPricingCalculator key={config.id} {...config} />;
}
// стандартные случаи...
}
2.1.12.3. Техническая реализация
Компонент проверяет наличие children и рендерит их без дополнительной обработки. При отсутствии контента возвращает null, обеспечивая чистую разметку без пустых блоков.
// @/app/@right/(_service)/(_components)/page-transformer-components/body-sections-components/body-section.tsx
export function BodySection({ children, className, ...props }: BodySectionProps) {
const hasChildren = children !== null && children !== undefined &&
!(Array.isArray(children) && children.length === 0) &&
!(typeof children === "string" && children === "");
if (!hasChildren) return null;
return (
<div className={cn(className)} {...props}>
{children}
</div>
);
}
2.1.12.4. Компонент
// @app/@right/(_service)/(_components)/page-transformer-components/body-sections-components/body-section.tsx
import type { HTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/utils";
import { SectionType } from "../../../(_types)/page-wrapper-types";
interface BodySectionProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode | null;
type: SectionType;
}
/**
* BodySection component.
* Renders children if present,
* otherwise renders an empty section with a default height (in rem) if provided,
* or renders nothing.
*/
export function BodySection({
children,
className,
...props
}: BodySectionProps) {
const defaultHeightRem = 0;
const hasChildren =
children !== null &&
children !== undefined &&
// Covers case when children = [] or ""
!(Array.isArray(children) && children.length === 0) &&
!(typeof children === "string" && children === "");
if (!hasChildren && defaultHeightRem) {
return (
<div
className={cn(className)}
style={{ height: ${defaultHeightRem}rem }}
{...props}
>
{/* Empty section with default height */}
</div>
);
}
if (!hasChildren) {
return null;
}
// Normal case: render content
return (
<div className={cn(className)} {...props}>
{children}
</div>
);
}
2.1.12.5. ЗаключениеBodySection служит мостом между стандартизированной архитектурой системы и творческой свободой AI-генерации, обеспечивая баланс между контролем и гибкостью.
2.2. Custom сложные компонентыCustom компоненты предназначены для реализации сложной интерактивной функциональности, которая выходит за рамки возможностей стандартной архитектуры. Эти компоненты используются в случаях, когда требуется управление состоянием, сложные анимации, real-time взаимодействие или уникальная бизнес-логика.
h4 Когда использовать Custom компоненты:
Интерактивные элементы — формы с валидацией, калькуляторы, конфигураторы продуктов
Анимированные презентации — сложные transitions, параллакс-эффекты, автоматические слайдеры
Real-time функциональность — чаты, уведомления, live-данные
Адаптивная логика — компоненты с кардинально разным поведением на desktop/mobile
Уникальные UI-паттерны — нестандартные элементы интерфейса, специфичные для проекта
Интеграция с архитектурой:
Custom компоненты добавляются в PageHtmlTransformer как отдельные case в switch-конструкции, получая доступ к контексту темы, устройства и другим системным параметрам. Они сохраняют единый стиль через использование общих CSS-классов и дизайн-токенов, но получают полную свободу в реализации внутренней логики.
Такой подход обеспечивает баланс между стандартизацией большинства контента и творческой свободой в реализации сложных интерактивных элементов.
2.2.1. Директива по обработке кастомных компонентов
2.2.1.1. About Wrappers
Запрет на обёртки для кастомных компонентов
Custom-компоненты должны возвращаться напрямую без Wrapper или FullScreenWrapper, так как они сами управляют своей структурой, отступами и слоями.
2.2.1.2. About custom component types
Размещение типов в компоненте
Интерфейсы пропсов кастомного компонента объявляются в верхней части самого компонента. Данные передаются через поле customComponentsAnyTypeData в конфиге секции.
2.2.1.3 Расширенный тип SectionType:
Добавление нового типа секции
Для каждого кастомного компонента добавляется уникальный тип в enum SectionType файла page-wrapper-types.ts для корректной типизации и обработки в switch-конструкции.
// @/app/@right/(_service)/(_types)/page-wrapper-types.ts
// ...
export type SectionType ="hero-section" | “new-custom-section”;
2.2.1.4. Update cases in the ageHtmlTransformer
Обработка кастомного кейса
В PageHtmlTransformer добавляется новый case для обработки кастомного типа секции с прямой передачей данных из customComponentsAnyTypeData в пропсы компонента.
case "new-custom-section":
return (
<NewCustomSection
key={config.id}
customData={section.customComponentsAnyTypeData.customData}
/>
);
2.2.1.5. Custom Config
Структура конфигурации
Пример конфигурации страницы с кастомной секцией, где все специфичные данные размещаются в объекте customComponentsAnyTypeData с произвольной структурой под нужды компонента.
export const PublicPagesConfig = {
pages: [
{
metadata: {
id: "CIUD",
title: "1",
description: "2",
slug: ["public", "name"],
type: "new-custom-section",
},
sections: [
{
id: "new-custom-section",
type: "new-custom-section",
customComponentsAnyTypeData: {
metaData: {
metaTitle: "1",
metaDescription: "2",
},
customData: {
mediaUrl: "/_static/illustrations/3.png",
title: "4",
description:
"5",
},
},
} as SectionConfig,
],
},
] as PageConfig[],
2.2.2. Custom component example
2.3.2.1. DoublePresentation

2.3.2.1.1. Обновление основных типов
// @/app/@right/(_service)/(_types)/page-wrapper-types.ts
// Добавляем новый тип секции
export type SectionType =
| "hero-section"
| "cta-section"
| "double-presentation-section" // Новый тип
// ... остальные типы
2.3.2.1.2. Add new case with current data to : PageHtmlTransformer
case "double-presentation-section":
return (
<DoublePresentation
key={section.id}
metaData={section.customComponentsAnyTypeData.metaData}
leftItem={section.customComponentsAnyTypeData.leftItem}
rightItem={section.customComponentsAnyTypeData.rightItem}
/>
);
2.3.2.1.3. page-wrapper-types.ts
// app/@right/(_service)/(_types)/page-wrapper-types.ts
export interface MetadataConfig {
title?: string;
description?: string;
}
export type CuidString = string;
export interface PageMetadata {
id: CuidString;
title: string;
description: string;
image?: string;
slug?: string[];
type: SectionType;
}
export type SectionType =
| "hero-section"
| "cta-section"
| "faq-section"
| "features-section"
| "testimonials-section"
| "pricing-section"
| "contact-section"
| "blog-posts-section"
| "product-grid-section"
| "image-gallery-section"
| "text-block-section"
| "video-section"
| "team-section"
| "about-us-section"
| "newsletter-section"
| "social-proof-section"
| "comparison-table-section"
| "map-section"
| "custom-html-section"
| "changelog-section"
| "comparison-two-column-section"
| "comparison-three-column-section"
| "feature-showcase-section"
| "double-presentation-section";
export interface BaseSection {
id: string;
type: SectionType;
className?: string;
}
export interface HeaderContentConfig {
announcement?: {
badgeText?: string;
descriptionText?: string;
href?: string;
};
heading: string;
headingLevel?: 1 | 2;
description?: string;
showBorder?: boolean;
}
export interface FooterContentConfig {
actions?: {
label: string;
href: string;
variant?:
| "default"
| "secondary"
| "destructive"
| "outline"
| "ghost"
| "link";
}[];
}
export interface SectionConfig extends BaseSection {
type: SectionType;
headerContent: HeaderContentConfig;
bodyContent?: React.ReactNode;
footerContent?: FooterContentConfig;
videoUrl?: string;
imageUrl?: string;
sectionClassName?: string;
contentWrapperClassName?: string;
customComponentsAnyTypeData?: any;
}
export type Section = SectionConfig;
export interface PageConfig {
metadata: PageMetadata;
sections: Section[];
}
export type SlugType = string[];
2.3.2.1.4. Config, example:
// @/app/@right/public/(_servise)/(_config)/public-pages-config.ts
import {
PageConfig,
SectionConfig,
} from "@/app/@right/(_service)/(_types)/page-wrapper-types";
export const PublicPagesConfig = {
pages: [
{
metadata: {
id: "public",
title: "Enterprise-Grade AI Next.js starter",
description: "Free Open-Source starter kit...",
slug: ["public", "test"],
type: "hero-section",
},
sections: [
{
id: "test-block",
type: "hero-section",
headerContent: {
announcement: {
badgeText: "Thanks",
descriptionText: "AI-SDK V5 & Vercel AI",
href: "https://github.com/aifa-agi/aifa",
},
heading: "Enterprise-Grade AI Next.js starter",
description:
"Free Open-Source starter kit to build, deploy, and scale intelligent AI applications. Artifacts Feature, features secure multi-provider auth, Stripe payments, vector knowledge bases, deep-research agents, and a unique fractal architecture designed for the future of AI.",
showBorder: false,
headingLevel: 1,
},
bodyContent: {},
footerContent: {
actions: [
{
label: "Get Started",
href: "/https://github.com/aifa-agi/aifa",
variant: "default",
},
{ label: "Browse Docs", href: "/docs", variant: "ghost" },
],
},
videoUrl: "/_static/video/ai-loop.mp4",
contentWrapperClassName: "text-white",
} as SectionConfig,
],
},
{
metadata: {
id: "interactive-ai",
title: "Interactive AI Demo",
description: "Demo: DoublePresentation custom case",
slug: ["public", "example"],
type: "double-presentation-section",
},
sections: [
{
id: "double-presentation-demo",
type: "double-presentation-section",
customComponentsAnyTypeData: {
metaData: {
metaTitle: "Interactive AI: Where Conversation Builds the UI",
metaDescription: "Discover what makes AIFA revolutionary...",
},
leftItem: {
mediaUrl: "/_static/illustrations/ai-chat.png",
title: "Ai Artifacts Chatbot",
description:
"As the AI chatbot speaks, it highlights elements...",
},
rightItem: {
mediaUrl: "/_static/illustrations/ai-web.png",
title: "Related Pages",
description:
"Click any UI element, and the AI provides instant context...",
},
},
} as SectionConfig,
],
},
] as PageConfig[],
};
2.3.2.1.5. Component
// @/app/@right/(_service)/(_components)/page-transformer-components/custom-sections/custom-double-prsentation.tsx
"use client";
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { useMediaQuery } from "@/hooks/use-media-query";
interface PresentationMeta {
metaTitle: string;
metaDescription: string;
}
interface PresentationItem {
mediaUrl: string;
title: string;
description: string;
}
interface DoublePresentationProps {
metaData: PresentationMeta;
leftItem: PresentationItem;
rightItem: PresentationItem;
}
export default function DoublePresentation({
metaData,
leftItem,
rightItem,
}: DoublePresentationProps) {
const { isMobile } = useMediaQuery();
// Desktop animation state
const [activeContainer, setActiveContainer] = useState<"left" | "right">(
"left"
);
const [sliderKey, setSliderKey] = useState(0);
// Desktop auto-switching effect
useEffect(() => {
// Only run animation cycle on desktop
if (isMobile) return;
let sliderTimer: NodeJS.Timeout;
let transitionTimer: NodeJS.Timeout;
const startAnimationCycle = () => {
setSliderKey((prev) => prev + 1);
sliderTimer = setTimeout(() => {
setActiveContainer((prev) => (prev === "left" ? "right" : "left"));
transitionTimer = setTimeout(() => {
startAnimationCycle();
}, 500);
}, 9000);
};
startAnimationCycle();
return () => {
clearTimeout(sliderTimer);
clearTimeout(transitionTimer);
};
}, [isMobile]);
// Return null while determining screen size
if (isMobile === null) {
return null;
}
// Common CSS classes
const metaBlockClass = "text-center max-w-3xl flex flex-col items-center";
const descriptionClass =
"mb-12 max-w-xl text-base text-muted-foreground text-center";
const desktopTitleClass =
"mb-6 max-w-3xl font-serif font-bold leading-tight md:text-2xl lg:text-4xl";
const desktopDescriptionClass =
"mb-12 max-w-xl text-lg text-muted-foreground md:text-xl text-center";
// Mobile card renderer
const renderMobileCard = (item: PresentationItem) => (
<div className="relative flex flex-col rounded-xl bg-gray-900 text-white shadow-lg mb-6 overflow-hidden">
<div className="w-full relative" style={{ paddingTop: "56.25%" }}>
<Image
src={item.mediaUrl}
alt={item.title}
fill
className="object-cover rounded-t-xl"
sizes="100vw"
priority
/>
</div>
<div className="flex flex-col p-4">
<h2 className="text-xl font-bold mb-2">{item.title}</h2>
<p className="text-gray-300 mb-2 text-base min-h-16">
{item.description}
</p>
</div>
</div>
);
// Desktop card renderer
const renderDesktopCard = (item: PresentationItem, isActive: boolean) => (
<motion.div
layout
animate={{ flex: isActive ? "7 1 0%" : "3 1 0%" }}
transition={{ duration: 0.5 }}
className="relative flex flex-col rounded-lg overflow-hidden bg-transparent text-white p-0 shadow-lg h-[30rem] flex-shrink-0"
>
<div className="relative w-full h-60 mb-4 rounded-xl overflow-hidden border-4 border-gray-700">
<Image
src={item.mediaUrl}
alt={item.title}
fill
className="object-cover"
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
<div className="flex flex-col pt-6">
<h2 className="text-2xl font-bold mb-2 whitespace-nowrap overflow-hidden text-ellipsis">
{item.title}
</h2>
<div className="relative w-full h-px bg-gray-700 mb-4">
<motion.div
key={slider-${item.title}-${sliderKey}}
className={cn(
"absolute top-0 left-0 h-full",
isActive ? "bg-primary" : "bg-gray-700"
)}
initial={{ width: 0 }}
animate={{ width: isActive ? "100%" : "0%" }}
transition={
isActive ? { duration: 9, ease: "linear" } : { duration: 0 }
}
/>
</div>
<p className="text-gray-300 mb-4 text-sm line-clamp-4 min-h-[4rem]">
{item.description}
</p>
</div>
</motion.div>
);
// Mobile layout
if (isMobile) {
return (
<section className="w-full pt-20">
<div className="container mx-auto px-4 flex flex-col items-center">
<div className={metaBlockClass}>
<h2 className="text-xl font-bold mb-4">{metaData.metaTitle}</h2>
<p className={descriptionClass}>{metaData.metaDescription}</p>
</div>
<div className="w-full flex flex-col">
{renderMobileCard(leftItem)}
{renderMobileCard(rightItem)}
</div>
</div>
</section>
);
}
// Desktop layout
return (
<section className="w-full pt-28">
<div className="container mx-auto px-4 flex flex-col items-center gap-12">
<div className={metaBlockClass}>
<h2 className={desktopTitleClass}>{metaData.metaTitle}</h2>
<p className={desktopDescriptionClass}>{metaData.metaDescription}</p>
</div>
<div className="flex gap-6 w-full max-w-6xl">
{renderDesktopCard(leftItem, activeContainer === "left")}
{renderDesktopCard(rightItem, activeContainer === "right")}
</div>
</div>
</section>
);
}

Roma Armstromg
AI Architector
Как всегда представляю исходный код готового решения чтобы вы могли сразу же приступить в созданию блогов магазинов лендингов и тп. Ссылка.