Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиСредний
Время на прочтение62 мин
Количество просмотров4.5K

Этот туториал — первая часть большого путешествия, в котором мы создадим 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

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

Теги:
Хабы:
-1
Комментарии4

Публикации

Ближайшие события