AI-генератор сайтов на ChatGPT и Next.js 15: Создаем SEO-оптимизированные страницы с нуля (аналог v0)

от автора

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

AI-генератор сайтов

AI-генератор сайтов

План такой:

  • Часть 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 после третьей публикации).

Вот пример:

aifa.dev

aifa.dev

Кому выгодна AI-система генерации сайтов: студии, корпорации, стартапы, фрилансеры

Дизайн-студии нового формата: от макета к массовому запуску сайтов за минуты

Представьте дизайн-агентство, которое создает не просто красивые макеты, а целые технологические экосистемы. Один раз вложившись в разработку уникальных компонентов и фирменного стиля, дизайнеры получают возможность генерировать профессиональные сайты со скоростью 50+ проектов в час.

Это не фантастика — это новая бизнес-модель, где креатив встречается с автоматизацией. И да, монетизация уже встроена через Stripe — об этом расскажу в следующих частях!

Корпоративные внедрения: единый стиль и масштабируемость для Enterprise-сайтов

Классический кейс — QR-меню для сети ресторанов. Один раз создав профессиональную систему карточек блюд, владелец бизнеса может предложить готовое решение сотням заведений. Рестораны адаптируют только цвета под свой бренд и наполняют контентом голосом — а профессиональный дизайн и UX остаются неизменными.

Масштабируемость, единообразие, скорость внедрения — именно то, что нужно корпоративному сегменту в 2025 году.

Transformer-архитектура компонентов: JSON → React без ручной верстки

В основе технологии AIFA Dev лежат два критически важных компонента:

  1. Интеллектуальный промпт-инжиниринг
    Специально обученные инструкции для ИИ, способные извлекать из векторных баз данных точные примеры секций и адаптировать их под конкретные задачи.

  2. Компонентная архитектура‑трансформер
    React‑компоненты, которые «понимают» конфигурационные файлы и превращают JSON‑описания в живые, интерактивные страницы.

Внешние модели (ChatGPT, Claude) отлично справляются с генерацией контента, но компоненты-распаковщики — это сердце системы, которое мы создаем собственными силами.

AI-ready документация: как использовать текст как базу знаний для ChatGPT

Не читайте этот туториал — используйте его как AI‑ассистента!

Основная ценность этого объемного материала не в последовательном чтении, а в том, что он становится персональной базой знаний для ваших проектов.

Как это работает:

  1. Скопируйте весь текст в ChatGPT

  2. Задавайте конкретные вопросы: «Как реализовать…?», «Зачем нужно…?», «Покажи код для…»

  3. Получайте точные ответы с готовыми компонентами

Это не документация для чтения — это интерактивный справочник для быстрого решения задач. Попробуйте такой подход, и вы поймете, почему современная техническая документация должна быть AI‑ready!

1.Введение: цель, выгоды и место метода среди генераторов v0-поколения

Данный туториал не ставит своей задачей создание решения, превосходящего v0 по скорости генерации. Основная цель — обеспечение единообразия и гармоничности дизайна на всех страницах проекта.

Да, создание каждой отдельной страницы может потребовать несколько больше времени по сравнению с универсальными генераторами. Однако совокупные затраты на проект значительно снижаются благодаря следующим преимуществам:

  • Единый стиль всех компонентов — каждая новая страница автоматически вписывается в общую дизайн‑систему

  • Гармоничность всего проекта — отсутствие стилистических разрывов между разными разделами сайта

  • Снижение затрат на доработку — минимальная необходимость в последующей стилизации и адаптации

  • Масштабируемость решения — чем больше страниц, тем выше эффективность подхода

Результат: проект получается визуально целостным, профессиональным и требует минимальных усилий на поддержание единообразия интерфейса при масштабировании.

В основном туториале рассматривается создание стандартных страниц с использованием базовых компонентов header и footer.

Однако в исходном коде проекта вы найдете кастомные реализации для специализированных типов страниц:

  • Страницы документации с расширенной навигацией

  • Страницы Tutorial с интерактивными элементами

Работа со сложными компонентами:

Обратите внимание, что некоторые компоненты на главной странице обладают более сложной логикой:

  • Управление состоянием (state management)

  • Анимации и переходы

  • Интерактивные элементы

Интеграция продвинутых решений:

Для интеграции таких сложных компонентов предусмотрена возможность добавления Custom-компонентов вместо стандартных секций. Это позволяет:

  • Сохранить гибкость системы

  • Внедрить уникальную функциональность

  • Поддержать единый стиль даже для нестандартных решений

Практическое задание:

Изучение процесса создания и интеграции Custom-компонентов остается вашим практическим заданием для закрепления полученных знаний.

1.1.Концепция: единообразные страницы бренда, создаваемые ИИ по шаблонам

Современный процесс разработки требует скорости, однородности интерфейса и возможности быстрого реагирования на бизнес-требования. Мы предлагаем архитектуру, в которой стандартные, формализованные описания дизайн-секций (Hero, CTA, FAQ, Features и др.) хранятся в структурированной базе знаний ИИ.

Загружая туда ваши актуальные стандарты UI/UX для каждой секции, вы позволяете искусственному интеллекту выступать не только генератором контента, но и гарантом единого фирменного стиля на всех уровнях создания страниц.

1.2. Пять этапов: от загрузки дизайн-шаблонов до SEO-оптимизированного билда

  1. Загрузка стандартов дизайна:
    В базу знаний ИИ добавляются шаблоны и инструкции для каждой секции — как их визуально и структурно реализовывать в рамках единого стиля вашего бренда.

  2. Обработка текстового запроса:
    Пользователь, менеджер проекта или программист формирует задание в виде текстового промпта — описывает желаемую страницу или её элементы.

  3. AI-интерпретация и структурирование:
    Искусственный интеллект анализирует запрос, генерирует семантический контент, разбивает его на логические секции и подбирает подходящие шаблоны из базы знаний.

  4. Генерация кода страниц:
    На основании выбранных шаблонов и сгенерированного контента система формирует строгие PageConfig’и — декларативные JSON/Type-описания будущих страниц. Они гарантируют соблюдение всех дизайн-стандартов и упрощают downstream-разработку.

  5. Автоматическая сборка и билдинг:
    Готовые конфигурации применяются для статической генерации страниц с использованием рендер-компонентов, в результате чего вы получаете полноценную, 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 выполняет несколько ключевых архитектурных ролей:

  1. Фасад паттерн — предоставляет простой интерфейс для сложной системы рендеринга

  2. Factory паттерн — создает нужные компоненты на основе типа секции

  3. Strategy паттерн — выбирает стратегию рендеринга (Wrapper/FullScreenWrapper) в зависимости от контекста

  4. Template Method паттерн — определяет скелет алгоритма рендеринга с возможностью переопределения отдельных шагов

Рабочий процесс:

  1. Получение конфигурации — принимает PageConfig с массивом секций

  2. Инициализация контекста — получает theme, определяет isMobile

  3. Итерация по секциям — для каждой секции определяет тип и способ рендеринга

  4. Выбор обёртки — FullScreenWrapper для hero-секций, Wrapper для остальных

  5. Рендеринг контента — применяет renderSectionContent или кастомный компонент

  6. Сборка финальной страницы — объединяет все секции в единую структуру

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:

  1. AI получает новые инструкции в базу знаний

  2. Добавляется тип в enum SectionType

  3. При необходимости расширяется интерфейс конфигурации

Баланс стандартизации: 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

Рабочий процесс:

  1. AI анализирует существующий конфиг

  2. Генерирует новый PageConfig на основе промпта

  3. Разработчик добавляет конфиг в массив pages

  4. Система автоматически подхватывает новые маршруты

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *