Реалізація підмени заголовків лендинга за рекламною кампанією
Підмена заголовків (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 день.







