Разработка мультилендинга с динамическим контентом
Мультилендинг — лендинг, который адаптирует контент в реальном времени под источник трафика. Один URL, разные заголовки, офферы, изображения, телефонные номера — в зависимости от UTM-параметров, города пользователя, рекламной площадки, ключевого слова или других сигналов. Используется для повышения релевантности рекламных кампаний без создания десятков отдельных страниц.
Зачем это нужно
Пользователь, кликнувший на объявление «Ремонт iPhone 14 в Минске», ожидает увидеть именно это на лендинге. Если он попадает на общую страницу «Ремонт смартфонов» — конверсия падает, Quality Score в Google Ads снижается, стоимость клика растёт.
Мультилендинг решает задачу масштабирования: вместо создания 50 страниц под 50 ключевых слов — одна страница с шаблонизацией.
Архитектура
Динамический контент можно подставлять на трёх уровнях:
Клиентский (JS после загрузки страницы) — самый простой, но самый плохой с точки зрения SEO и CLS. Пользователь видит «моргание» контента при подстановке.
Edge (Middleware до рендера) — страница рендерится на edge-ноде с уже подставленным контентом. Нет CLS, SEO корректно индексирует версию по умолчанию. Реализуется через Vercel Edge Middleware, Cloudflare Workers, Netlify Edge Functions.
Server-side (SSR) — запрос обрабатывается сервером, контент подставляется до отправки HTML. Похоже на Edge, но выполняется на выделенном сервере, а не на распределённой сети.
Оптимальный выбор — Edge Middleware для проектов на Vercel/Cloudflare или SSR для самохостинговых решений.
Реализация на Next.js + Vercel Edge Middleware
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { searchParams } = request.nextUrl;
const utmTerm = searchParams.get('utm_term') ?? '';
const utmSource = searchParams.get('utm_source') ?? '';
const city = request.geo?.city ?? '';
// Передаём данные в заголовках — читаем их в компоненте
const response = NextResponse.next();
response.headers.set('x-utm-term', utmTerm);
response.headers.set('x-utm-source', utmSource);
response.headers.set('x-visitor-city', city);
return response;
}
export const config = {
matcher: ['/landing/:path*'],
};
// app/landing/page.tsx
import { headers } from 'next/headers';
import { getContent } from '@/lib/content-engine';
export default function LandingPage() {
const headersList = headers();
const utmTerm = headersList.get('x-utm-term') ?? '';
const city = headersList.get('x-visitor-city') ?? 'вашем городе';
const content = getContent(utmTerm);
return (
<main>
<h1>{content.headline.replace('{city}', city)}</h1>
<p>{content.subheadline}</p>
<ContactForm phone={content.phone} />
</main>
);
}
Контент-движок: матрица подстановок
Логика подстановки контента — отдельный модуль. Не жёстко зашитые if/else, а конфигурируемые правила:
// lib/content-engine.ts
interface ContentVariant {
headline: string;
subheadline: string;
cta: string;
phone: string;
image: string;
}
const defaultContent: ContentVariant = {
headline: 'Ремонт смартфонов в {city}',
subheadline: 'Диагностика бесплатно. Гарантия 6 месяцев.',
cta: 'Записаться на ремонт',
phone: '+375291234567',
image: '/hero-default.webp',
};
const variants: Record<string, Partial<ContentVariant>> = {
'ремонт iphone': {
headline: 'Ремонт iPhone в {city} — от 1 часа',
subheadline: 'Оригинальные запчасти. Ремонт при вас.',
image: '/hero-iphone.webp',
},
'ремонт samsung': {
headline: 'Ремонт Samsung в {city}',
subheadline: 'Любые модели Galaxy. Гарантия 12 месяцев.',
image: '/hero-samsung.webp',
},
'замена экрана': {
headline: 'Замена экрана смартфона за 30 минут',
cta: 'Узнать стоимость замены',
},
};
export function getContent(utmTerm: string): ContentVariant {
const key = utmTerm.toLowerCase();
const variant = Object.entries(variants).find(([k]) => key.includes(k));
return { ...defaultContent, ...(variant?.[1] ?? {}) };
}
Геолокация и подстановка номера телефона
Для бизнеса с несколькими городами — подстановка локального номера критична. Звонок «в местный офис» конвертирует лучше, чем федеральный номер:
const phoneByCity: Record<string, string> = {
'Minsk': '+375 (17) 000-00-00',
'Grodno': '+375 (15) 000-00-00',
'Brest': '+375 (16) 000-00-00',
};
// В middleware — из request.geo (только на Vercel с Geolocation)
const city = request.geo?.city ?? '';
const phone = phoneByCity[city] ?? defaultPhone;
Альтернатива для самохостинга — MaxMind GeoIP2 database или сервис ipapi.co/ipgeolocation.io через API.
CallTracking
Для каждого источника трафика (Google Ads, Яндекс.Директ, органика, соцсети) — разный номер телефона через CallTracking сервис (CoMagic, Callibri, Ringostat). Номер показывается через JavaScript после загрузки страницы:
// Динамическая замена номера CallTracking
window.ct_replace = {
default: '+375291234567',
pools: {
'google': '+375291111111',
'yandex': '+375292222222',
}
};
Обычно CallTracking-сервис предоставляет готовый скрипт с подобной логикой.
CMS для управления вариантами
Матрица вариантов контента быстро вырастает. Для маркетолога, который не редактирует код, нужен интерфейс управления:
| UTM Term | Заголовок | Подзаголовок | Изображение | Телефон |
|---|---|---|---|---|
| ремонт iphone | ... | ... | hero-iphone.webp | +375... |
| замена экрана | ... | ... | hero-screen.webp | +375... |
Реализуется как простая CRUD-таблица в admin-панели (Filament, Directus, даже Google Sheets через API). На старте часто достаточно JSON-конфига в репозитории с деплоем при изменении.
A/B тестирование вариантов
Мультилендинг + A/B тест — комбинация для максимальной оптимизации. На Edge Middleware можно реализовать случайное разделение:
// Случайное разделение 50/50
const variant = Math.random() < 0.5 ? 'A' : 'B';
response.cookies.set('ab_variant', variant, { maxAge: 60 * 60 * 24 * 7 });
response.headers.set('x-ab-variant', variant);
В компоненте страницы читаем x-ab-variant и рендерим соответствующую версию. Результаты тестов — в GA4 через custom dimensions.
SEO и мультилендинг
Динамический контент на одном URL создаёт риски для SEO. Googlebot видит версию по умолчанию. Если мультилендинг — рекламная история без органического трафика, SEO неважно. Если страница должна ранжироваться — нужны отдельные URL для каждого варианта с canonical и hreflang.
Правило: мультилендинг для рекламного трафика, отдельные оптимизированные страницы для органики.
Типичные сроки
Базовый мультилендинг (UTM → заголовок/CTA) на статике или Next.js — 5–7 рабочих дней. С геолокацией, CallTracking, CMS для управления вариантами — 10–14 дней. Полная система с A/B тестированием, аналитикой, мультиязычностью и 50+ вариантами — 3–4 недели.







