Реалізація персоналізованих CTA (Call-to-Action) на сайті
Уявіть: холодний відвідувач з пошуку бачить «Залишити заявку», а той, хто повертається, — той самий текст. Перший йде, не зрозумівши цінності, другий витрачає час на зайвий клік. Один і той самий CTA втрачає до 50% потенційних конверсій. Персоналізований CTA змінює текст, офер та цільову дію залежно від сегмента, джерела трафіку та поведінки. Ми впроваджуємо такі рішення під ключ на стеку Laravel + React і гарантуємо зростання конверсії — у наших проектах воно становить 20–40%.
Чому персоналізація CTA збільшує конверсію?
Холодний користувач не готовий купувати — йому потрібне демо. Той, хто повертається, вже знайомий з продуктом — йому потрібен вхід до кабінету. Якщо показувати одне й те саме, ви втрачаєте до 50% конверсій. Персоналізація знижує когнітивне навантаження: релевантна пропозиція викликає більше кліків. За даними наших кейсів, після впровадження CTR зростає на 25–35%, а вартість ліда знижується на 15–20%.
Які дані використовувати для вибору варіанта?
Перш ніж щось показувати, потрібно зрозуміти, хто прийшов. Ми використовуємо комбінацію клієнтських та серверних даних:
- Джерело трафіку: UTM-мітки (utm_source, utm_campaign) — визначаємо канал: пошук, реклама, ретаргетинг.
- Поведінка на сайті: кількість візитів (localStorage), переглянуті сторінки (sessionStorage).
- Авторизація: наявність auth_token cookie, сегмент користувача (free/trial/paid).
- Геолокація: країна та місто з IP-адреси (MaxMind GeoIP, передається в
window.__GEO__).
function buildUserContext() {
return {
utm_source: new URLSearchParams(location.search).get('utm_source'),
utm_campaign: new URLSearchParams(location.search).get('utm_campaign'),
visits: parseInt(localStorage.getItem('visit_count') ?? '0'),
pages_viewed: JSON.parse(sessionStorage.getItem('pages_viewed') ?? '[]'),
last_visit: localStorage.getItem('last_visit'),
is_logged_in: !!document.cookie.match(/auth_token/),
user_segment: localStorage.getItem('user_segment'),
country: window.__GEO__?.country,
city: window.__GEO__?.city,
};
}
Матриця CTA за контекстом
На основі контексту будуємо матрицю правил. Приклад на TypeScript:
interface CtaVariant {
text: string;
subtext?: string;
url: string;
style: 'primary' | 'secondary' | 'outline';
}
function getCtaVariant(ctx: UserContext): CtaVariant {
if (ctx.is_logged_in) {
if (ctx.user_segment === 'trial') {
return { text: 'Покращити до Pro', subtext: 'Залишилось 5 днів тріалу', url: '/upgrade', style: 'primary' };
}
return { text: 'Відкрити кабінет', url: '/dashboard', style: 'outline' };
}
if (ctx.visits >= 3) {
return { text: 'Почати безкоштовно', subtext: 'Без кредитної картки', url: '/signup', style: 'primary' };
}
if (ctx.utm_campaign?.includes('retargeting')) {
return { text: 'Повернутися до замовлення', url: '/cart', style: 'primary' };
}
if (ctx.utm_source === 'google' || ctx.utm_source === 'yandex') {
return { text: 'Отримати демо', subtext: 'Відповімо за 15 хвилин', url: '/demo', style: 'primary' };
}
return { text: 'Дізнатися більше', url: '/how-it-works', style: 'secondary' };
}
Як уникнути layout shift при динамічній заміні CTA?
Layout shift — часта проблема при клієнтській персоналізації. Поки JS завантажується та визначає варіант, сторінка може смикатися. Рішення: скелетон-компонент CtaSkeleton з фіксованою висотою, що дорівнює висоті найвищого варіанта. Тоді користувач бачить заглушку, яка не змінює розміри. У React це виглядає так:
interface PersonalizedCtaProps {
location: 'hero' | 'sidebar' | 'footer' | 'sticky';
}
function PersonalizedCta({ location }: PersonalizedCtaProps) {
const [variant, setVariant] = useState<CtaVariant | null>(null);
useEffect(() => {
const ctx = buildUserContext();
const v = getCtaVariant(ctx);
setVariant(v);
gtag('event', 'cta_impression', {
cta_text: v.text,
cta_url: v.url,
cta_location: location,
visit_count: ctx.visits,
utm_source: ctx.utm_source,
});
}, []);
if (!variant) return <CtaSkeleton />;
const handleClick = () => {
gtag('event', 'cta_click', {
cta_text: variant.text,
cta_url: variant.url,
cta_location: location,
});
};
return (
<div className={`cta cta--${location} cta--${variant.style}`}>
<a href={variant.url} onClick={handleClick} className="cta__button">
{variant.text}
</a>
{variant.subtext && <p className="cta__subtext">{variant.subtext}</p>}
</div>
);
}
Серверна персоналізація для SSR
При серверному рендерингу логіку краще виконувати на сервері — користувач одразу отримує потрібний варіант без мерехтіння. У Laravel використовуємо middleware, який передає контекст через Inertia. Приклад:
class PersonalizationMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$context = [
'visits' => (int) $request->cookie('visit_count', 0),
'utm_source' => $request->query('utm_source') ?? $request->session()->get('utm_source'),
'utm_campaign' => $request->query('utm_campaign') ?? $request->session()->get('utm_campaign'),
'segment' => $request->user()?->segment ?? 'anonymous',
'country' => geoip($request->ip())->country,
];
Inertia::share('personalization', $context);
return $next($request);
}
}
На фронтенді дані вже доступні при першому рендері — без useEffect:
import { usePage } from '@inertiajs/react';
function HeroCta() {
const { personalization } = usePage().props;
const variant = getCtaVariant(personalization);
return <a href={variant.url}>{variant.text}</a>;
}
Порівняння підходів: клієнтська vs серверна персоналізація
| Критерій | Клієнтська (JS) | Серверна (Inertia) |
|---|---|---|
| Мерехтіння | Можливе (скелетон вирішує) | Немає, одразу готовий HTML |
| Швидкість завантаження | Додатковий запит даних | Дані в першому HTML |
| Складність впровадження | Низька | Середня (потрібен middleware) |
| Гнучкість | Легко змінювати правила | Потрібен деплой сервера |
| Підходить для | Прості сегменти (UTM, візити) | Складні правила (авторизація, гео) |
Які метрики відстежувати після впровадження?
Після запуску обов'язково слідкувати за:
- CTR за сегментами — порівняння кожного варіанта.
- Конверсія в цільову дію — покупка, реєстрація.
- Layout shift (CLS) — особливо важливо для клієнтської версії.
- Вплив на SEO — персоналізація не повинна погіршувати Core Web Vitals.
A/B тестування варіантів
Персоналізація не відміняє тестування. Для детермінованого розподілу за userId використовуємо хеш: hash(userId + testName) % 2. Це гарантує, що один користувач завжди бачить один варіант. Трекінг impressions та clicks обов'язковий.
Процес роботи
- Аналітика — аудит поточних CTA, збір даних про трафік та сегменти.
- Проектування — розробка матриці правил, узгодження варіантів для кожного сегмента.
- Реалізація — інтеграція клієнтської логіки (JS) та серверної (Laravel/Inertia), підключення гео-сервісу.
- Тестування — перевірка всіх станів (логін, візити, UTM), відсутність layout shift.
- Деплой та моніторинг — викатка на продакшен, налаштування подій у GA4, A/B тест.
Строки та вартість впровадження
| Тип персоналізації | Час |
|---|---|
| Базова (UTM + візити) | 4–6 годин |
| Повна матриця (авторизація, сегменти, гео) | 1–2 дні |
| Серверна інтеграція (Inertia) | +4–6 годин |
| Налаштування трекінгу GA4 | 2–3 години |
Точна вартість розраховується індивідуально після аналізу вашого проекту.
Що входить в роботу
- 📄 Документація: опис матриці CTA та правил.
- 🔐 Доступи до репозиторію та системи трекінгу.
- 🎓 Навчання: відеоінструкція з додавання нових сегментів.
- 🛠 Гарантія: безкоштовні правки протягом місяця.
- 🧑💻 Підтримка чату на час експлуатації.
Замовте впровадження персоналізованих CTA — зв'яжіться з нами для оцінки вашого проекту. Досвід понад 5 років, реалізовано 50+ проектів. Отримайте консультацію з вибору оптимального підходу.







