Реализация подмены заголовков лендинга по рекламной кампании
Подмена заголовков (headline substitution или dynamic keyword insertion на уровне сайта) — это техника, при которой H1 и подзаголовки лендинга автоматически меняются под рекламное объявление, которое привело пользователя. Если человек кликнул на объявление «Разработка сайта для стоматологии» — он попадает на страницу с заголовком «Разработка сайта для стоматологии», а не на общий лендинг веб-студии.
Принцип работы
Рекламная система передаёт параметры в URL. Либо стандартные UTM, либо кастомные параметры. Сайт читает параметры, находит в словаре нужный заголовок и подставляет его в DOM до того, как пользователь успевает увидеть исходный текст.
Два подхода: параметр в URL напрямую содержит текст (небезопасно — можно подделать) или параметр содержит ключ, по которому сервер/клиент находит заголовок из предзаданного словаря.
Словарь заголовков
// headline-map.js — словарь кампания → заголовок
export const headlineMap: Record<string, HeadlineSet> = {
// Google Ads — по utm_campaign
'dental-clinic-sites': {
h1: 'Сайты для стоматологий — под ключ за 14 дней',
h2: 'Запись онлайн, личный кабинет пациента, интеграция с МИС',
breadcrumb: 'Сайты для клиник',
},
'lawyer-sites': {
h1: 'Сайты для юридических компаний',
h2: 'Конфиденциальность, доверие, форма записи на консультацию',
breadcrumb: 'Сайты для юристов',
},
'ecom-landing': {
h1: 'Интернет-магазин с нуля — от прототипа до запуска',
h2: 'Каталог, корзина, оплата, доставка — всё готово',
breadcrumb: 'Интернет-магазины',
},
// По utm_term (ключевое слово)
terms: {
'создать сайт': {
h1: 'Создадим сайт под ваши задачи',
h2: 'Рассчитайте стоимость за 2 минуты',
},
'разработка лендинга': {
h1: 'Лендинг с конверсией от 5%',
h2: 'Продаёт 24/7 — пока вы занимаетесь бизнесом',
},
},
};
Логика подстановки
interface HeadlineSet {
h1?: string;
h2?: string;
breadcrumb?: string;
}
function resolveHeadlines(): HeadlineSet {
const params = new URLSearchParams(location.search);
const campaign = params.get('utm_campaign') ?? sessionStorage.getItem('utm_campaign');
const term = params.get('utm_term') ?? sessionStorage.getItem('utm_term');
// Сохраняем для следующих страниц
if (params.get('utm_campaign')) {
sessionStorage.setItem('utm_campaign', params.get('utm_campaign')!);
sessionStorage.setItem('utm_term', params.get('utm_term') ?? '');
}
// Приоритет: кампания > ключевое слово > дефолт
if (campaign && headlineMap[campaign]) return headlineMap[campaign];
if (term) {
const termKey = Object.keys(headlineMap.terms ?? {})
.find(k => term.toLowerCase().includes(k));
if (termKey) return headlineMap.terms[termKey];
}
return {}; // не меняем дефолтный текст
}
function applyHeadlines(headlines: HeadlineSet): void {
const map: [string, keyof HeadlineSet][] = [
['[data-headline="h1"]', 'h1'],
['[data-headline="h2"]', 'h2'],
['[data-headline="breadcrumb"]', 'breadcrumb'],
];
map.forEach(([selector, key]) => {
const el = document.querySelector(selector);
if (el && headlines[key]) {
el.textContent = headlines[key]!;
}
});
}
// Запуск — желательно inline в <head> чтобы не было мерцания
const headlines = resolveHeadlines();
applyHeadlines(headlines);
Разметка HTML
<!DOCTYPE html>
<html>
<head>
<!-- Скрипт подмены заголовков — до рендера body -->
<script src="/js/headline-substitution.js"></script>
</head>
<body>
<nav class="breadcrumb">
<a href="/">Главная</a> /
<span data-headline="breadcrumb">Разработка сайтов</span>
</nav>
<section class="hero">
<h1 data-headline="h1">Разработка сайтов для бизнеса</h1>
<p data-headline="h2">Работаем с 2010 года — более 400 проектов</p>
<a href="/brief" class="btn-primary">Рассчитать стоимость</a>
</section>
</body>
</html>
Скрипт подстановки должен быть синхронным в <head>, а не defer/async — иначе пользователь увидит исходный заголовок на долю секунды перед заменой (flash of original content). Скрипт маленький — 1–2 КБ, блокировка рендера незначительна.
Google Ads Dynamic Keyword Insertion vs подмена заголовков
DKI в Google Ads подставляет ключевое слово в объявление. Подмена заголовков на сайте — логическое продолжение: сайт «отвечает» тем же языком, что и объявление. Связка работает через utm_term:
В кампании Google Ads создаётся шаблон URL:
https://example.com/landing?utm_campaign=dental&utm_term={keyword}
Google автоматически подставляет реальное ключевое слово в {keyword}. Сайт читает utm_term и показывает соответствующий заголовок.
Серверная подмена (без мерцания)
Для SSR-проектов подмена на сервере надёжнее:
// LandingController.php
public function index(Request $request): Response
{
$campaign = $request->get('utm_campaign') ?? $request->session()->get('utm_campaign');
$term = $request->get('utm_term') ?? $request->session()->get('utm_term');
if ($request->get('utm_campaign')) {
$request->session()->put('utm_campaign', $campaign);
$request->session()->put('utm_term', $term);
}
$headlines = HeadlineResolver::resolve($campaign, $term);
return Inertia::render('Landing', [
'headlines' => $headlines,
]);
}
Проверка корректности подмены
Для QA создаётся специальный режим: при добавлении ?debug_headlines=1 в URL страница показывает все доступные варианты заголовков в виде таблицы — удобно для тестирования перед запуском кампании.
Сроки
Словарь заголовков + JS-подмена на клиенте: 3–5 часов. Серверная подмена с сессией + Inertia: 4–6 часов. QA-режим для проверки всех вариантов: 1–2 часа. Загрузка словаря из CMS (редактируемый список в админке): 1 день.







