Лендинг AI-конференции: от поста до продакшна

Бывает так: мероприятие уже прошло, фотки есть, восторг у участников есть, а сайта — нет. Именно в такой ситуации я оказался после конференции «Навстречу к AI». Офлайн-событие прогремело, техничка немного подвела, но контент получился огонь. И вот стоит задача: собрать нормальный лендинг, который зафиксирует этот момент и расскажет о нём тем, кто ещё не знает.
В этой статье я разберу весь путь — от идеи и дизайн-концепции до продакшн-деплоя с автовебхуками и мобильной адаптацией. Плюс в конце — бонус: как из этой работы вырос полноценный Dokploy-агент, который теперь экономит мне время на каждом новом проекте.
Контекст: что за мероприятие и почему нужен лендинг
Конференция «Навстречу к AI» — живое событие про нейросети, ChatGPT, Midjourney и всё, что сейчас взрывает рынок. Спикеры выступили сильно, площадка «Калибр» дала нужную атмосферу, люди приходили за практикой — и получили её. Была одна беда: техника подвела в начале, онлайн-запись ушла в брак.
Но мероприятие состоялось, и теперь нужен сайт. Не просто страничка «мы провели ивент», а полноценный лендинг с программой, спикерами, фотогалереей и дресс-кодом для будущих ивентов.
Технологический стек выбрал сразу:
- Next.js 14 — App Router, серверные компоненты, встроенная оптимизация изображений
- Tailwind CSS — утилитарный CSS, быстро верстать
- GSAP — анимации заголовка и элементов
- Dokploy — self-hosted PaaS для деплоя через Docker
- Шрифт Syne, цвет акцента #FF4F00 — всё в стиле pashavin.ru
Сборка лендинга: структура и компоненты
Лендинг разбился на логичные секции:
Hero-блок — первое что видит пользователь. Анимированный заголовок «НАВСТРЕЧУ К AI» с посимвольным появлением через GSAP, бейдж «Мероприятие состоялось», дата, место, цена. Сразу даём контекст.
Marquee — бегущая строка с ключевыми словами (нейросети, ChatGPT, Midjourney, автоматизация). Это и навигационный элемент, и атмосфера.
О мероприятии — короткое описание + 4 статистических карточки. Цифры работают лучше текста.
Спикеры — 3 карточки с именами и темами выступлений.
Программа — таймлайн с 9 пунктами от 18:00 до 20:30. Показывает плотность контента.
Дресс-код — визуальный раздел с фотопримерами. Об этом отдельно.
Фотогалерея — 8 фото события в адаптивной сетке.
Структура проекта стандартная для Next.js:
18ai/
├── app/
│ ├── layout.tsx
│ ├── page.tsx
│ └── globals.css
├── components/
│ ├── Hero.tsx
│ ├── Speakers.tsx
│ ├── Schedule.tsx
│ ├── DressCode.tsx
│ └── Gallery.tsx
├── public/
│ └── images/
├── Dockerfile
└── next.config.js
Деплой через Dokploy: пошаговый процесс
Когда сайт собран локально и билд проходит чисто, начинается деплой. У нас уже настроен Dokploy на VPS с wildcard DNS *.pashavin.ru → 5.35.80.222.
Процесс деплоя нового проекта выглядит так:
1. Подготовка репозитория
Создаём публичный GitHub-репо (в нашем случае bugle-c/18ai), пушим код. Публичный — чтобы не возиться с SSH-ключами Dokploy на каждом новом проекте.
git init
git add .
git commit -m "init: 18ai landing"
git remote add origin https://github.com/bugle-c/18ai.git
git push -u origin master2. Dockerfile
Без него Dokploy не знает как собирать. Минимальный рабочий вариант для Next.js:
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]3. Создание приложения в Dokploy через API
Создаём project и application через Dokploy REST API:
# Создаём приложение
curl -X POST https://dokploy.pashavin.ru/api/application.create \
-H "Authorization: Bearer [СКРЫТО]" \
-H "Content-Type: application/json" \
-d '{
"name": "18ai",
"projectId": "PROJECT_ID",
"buildType": "dockerfile",
"customGitUrl": "https://github.com/bugle-c/18ai.git",
"customGitBranch": "master"
}'4. Добавление домена и запуск
Добавляем домен 18ai.pashavin.ru, Let's Encrypt сам получает сертификат (wildcard уже настроен). Запускаем деплой — Dokploy клонирует репо, билдит Docker-образ, поднимает контейнер.
5. Webhook для автодеплоя
Настраиваем GitHub webhook — при каждом пуше в master Dokploy автоматически передеплоивает. Это критично для итеративной работы: пушишь → сайт обновился через 2-3 минуты.
Боли с изображениями и дресс-кодом
Самая итеративная часть работы — секция дресс-кода. Казалось бы, просто фотки, но нюансов много.
Первоначально скачали фото с Unsplash — качественные, но не попадали в нужный стиль. После нескольких итераций перешли на подобранные вручную Pinterest-изображения:
const menPhotos = [
"https://i.pinimg.com/736x/be/07/78/be077825a1f8fd206bb3d7b8d57a829c.jpg",
"https://i.pinimg.com/736x/78/af/5d/78af5ddc996ed1df4c318cd4e64b2a82.jpg",
"https://i.pinimg.com/736x/f2/9e/6c/f29e6ccde7b26bc5537ac9200303bc46.jpg",
];
const womenPhotos = [
"https://i.pinimg.com/736x/bd/93/46/bd934626fc87ff67274a1b5defa96040.jpg",
// ...
];Одновременно эволюционировал стиль: «Фестивальный стиль» → «Smart Casual». Тексты описаний переписали под новую концепцию:
До:
Пиджак с рубашкой, галстук по желанию. Нарядный, но не официальный — фестивальный дух.
После:
Чиносы или брюки, рубашка или поло, пиджак по желанию. Аккуратно, но без галстука — расслабленная элегантность.
Для внешних изображений в next.config.js нужно явно разрешить домены:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'i.pinimg.com',
},
],
},
};
module.exports = nextConfig;Мобильная адаптация: горизонтальный скролл вместо сетки
После первой версии мобильной вёрстки выяснилось: вертикальные стеки для фото работают плохо. Слишком много скролла, фото теряются. Решение — горизонтальный скролл со snap.
.photo-scroll {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 12px;
padding-bottom: 8px;
-webkit-overflow-scrolling: touch;
}
.photo-scroll::-webkit-scrollbar {
display: none;
}
.photo-scroll > * {
scroll-snap-align: start;
flex-shrink: 0;
}В JSX-компоненте:
{/* Мобилка — горизонтальный скролл */}
<div className="photo-scroll md:hidden">
{photos.map((src, i) => (
<div key={i} className="w-[70vw] h-[220px] relative rounded-xl overflow-hidden">
<Image src={src} alt="" fill className="object-cover" />
</div>
))}
</div>
{/* Десктоп — сетка */}
<div className="hidden md:grid grid-cols-3 gap-4">
{photos.map((src, i) => (
<div key={i} className="h-[320px] relative rounded-xl overflow-hidden">
<Image src={src} alt="" fill className="object-cover" />
</div>
))}
</div>Тот же паттерн применили к карточкам спикеров и примерам дресс-кода. Это стало стандартом для всех медиа-секций: на мобилке — свайп, на десктопе — сетка.
Дополнительно уменьшили заголовок Hero для мобилки:
// Было
clamp(2.5rem, 12vw, 10rem)
// Стало
clamp(1.8rem, 9vw, 10rem)Теперь «НАВСТРЕЧУ К AI» влезает в одну строку даже на iPhone SE.
Убираем лишнее: история с лайтбоксом
Отдельная история — лайтбокс в галерее фотографий. Изначально был реализован: клик на фото → оверлей с полноразмерным изображением. Но по дизайн-решению он не нужен — фото носят атмосферный характер, не документальный.
Проблема в том, что лайтбокс пару раз «возвращался» в ходе рефакторинга. Пришлось зачистить полностью — убрать state, callbacks, условный рендеринг:
// Было: куча состояния
const [selectedPhoto, setSelectedPhoto] = useState<string | null>(null);
const [lightboxOpen, setLightboxOpen] = useState(false);
const handlePhotoClick = useCallback((src: string) => {
setSelectedPhoto(src);
setLightboxOpen(true);
}, []);
// Стало: просто div
<div className="relative h-full">
<Image src={src} alt="" fill className="object-cover" />
</div>Простое правило: если фича не нужна — удаляй код полностью, не комментируй. Иначе она вернётся при следующем рефакторинге.
Dokploy-агент: автоматизация из опыта
После десятка задеплоенных проектов паттерны деплоя устоялись. Каждый раз одни и те же шаги: создать репо, написать Dockerfile, создать проект в Dokploy через API, настроить домен, добавить webhook, добавить в git-auto-sync.sh.
Решил формализовать это в виде скилла и агента для Claude:
# ~/.claude/skills/dokploy-deploy/SKILL.md
## Workflow (9 шагов)
1. Анализ проекта (стек, порт, env vars)
2. Создание/проверка git репо
3. Написание Dockerfile под стек
4. Создание Dokploy project через API
5. Создание application с buildType=dockerfile
6. Настройка домена + HTTPS
7. Создание GitHub webhook
8. Добавление в git-auto-sync.sh
9. Push + запуск деплоя + мониторинг логов
## Известные проблемы
- Приватный репо без SSH ключа → сделай публичным
- Dockerfile не найден → проверь buildPath и dockerfilePath
- Port mismatch → EXPOSE в Dockerfile = порт в DokployАгент dokploy-deployer знает все наши API-эндпоинты, умеет диагностировать ошибки по логам деплоя и сам добавляет новые проекты в систему автосинка. Это уже мета-уровень автоматизации: не просто деплоим, а автоматизируем процесс деплоя.
Результат
Сайт 18ai.pashavin.ru работает в продакшне:
- ✅ Адаптивный дизайн (мобилка + десктоп)
- ✅ GSAP-анимации в Hero
- ✅ Горизонтальный свайп на мобилке для всех медиа-секций
- ✅ Автодеплой при пуше в master
- ✅ HTTPS через Let's Encrypt
- ✅ Оптимизированные изображения через Next.js Image
- ✅ Время от идеи до продакшна — одна сессия
Выводы
Главный урок этого проекта — итеративность важнее перфекционизма. Я не стал месяц придумывать идеальную структуру лендинга. Собрал рабочую версию, задеплоил, начал получать фидбек по конкретным вещам: это фото не то, этот текст устарел, мобилка сломана. Каждый раунд фидбека давал конкретную задачу, которую можно решить за 15 минут.
Второй урок — стандартизируй то, что повторяется. Dokploy-агент появился потому, что я задеплоил десяток проектов и заметил паттерн. Теперь новый проект деплоится быстрее, потому что весь накопленный опыт зафиксирован в документации и агенте. Это и есть настоящая автоматизация — не скрипты ради скриптов, а фиксация знаний.
Третий урок — мобильная версия требует отдельного подхода для медиа. Просто уменьшить сетку недостаточно. Горизонтальный скролл со snap — это не компромисс, а правильное решение для галерей и карточек на мобилке. Пользователь свайпает инстинктивно, это нативный паттерн.
Четвёртый — удаляй мёртвый код сразу. Лайтбокс возвращался дважды именно потому, что оставались закомментированные куски. Если фича не нужна — git история сохранит её, а в рабочем коде её быть не должно. Чистый код легче поддерживать и реже ломается при рефакторинге.
Проект небольшой, но он хорошо показывает, как современный стек позволяет от концепции до продакшна доходить за часы, а не недели. Next.js + Tailwind + Dokploy — это комбо, которое я использую снова и снова именно потому, что оно предсказуемо работает.
Полезные ссылки:

AI-инженер, предприниматель, маркетолог. Основатель feberra.com и x10seo.ru. 13 лет в перфоманс-маркетинге, 3 года в системной интеграции AI в бизнес.
Связанный проект
AI Content Aggregator →