Этот туториал — первая часть большого путешествия, в котором мы создадим 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
Как всегда представляю исходный код готового решения чтобы вы могли сразу же приступить в созданию блогов магазинов лендингов и тп. Ссылка.
ссылка на оригинал статьи https://habr.com/ru/articles/930816/
Добавить комментарий