Social Proof на сайті — сповіщення та лічильники
Високий трафік, але конверсія кульгає — знайома біль. Середній e-commerce сайт втрачає до 70% відвідувачів через недовіру, не здійснивши покупку. Користувачі не бачать, що товар купують, не відчувають присутності інших людей на сайті. Соціальне підтвердження (Social Proof) — спливаючі сповіщення про покупки, лічильники онлайн-відвідувачів, бейджі «X купили сьогодні» — вирішує цю проблему. Ми впроваджуємо такі елементи з урахуванням UX та продуктивності, використовуючи реальні дані та сучасні протоколи. Важливо: фіктивні сповіщення швидко розпізнаються і підривають довіру. Наш підхід базується на реальних подіях з вашої CRM — це дає чесне зростання конверсії на 10–15% без ризику для репутації. SSE (Server-Sent Events) у 10 разів знижує навантаження на сервер порівняно з традиційним polling. За досвідом впроваджень, середній чек збільшується на 15%, а повернення інвестицій настає протягом 2–3 місяців. Економія на серверних ресурсах при переході з polling на SSE може досягати 90% — це економить від 10 000 до 50 000 гривень на місяць залежно від навантаження. Отримайте консультацію — ми розповімо, як це працює на вашому проєкті.
Проблеми, які вирішуємо
- Фейкові сповіщення — користувачі швидко розпізнають підробку, довіра падає. Ми використовуємо лише реальні дані з CRM або замовлень.
- Частота без міри — сповіщення кожні 2 секунди дратують. Налаштовуємо інтервали 8–15 секунд і обмежуємо чергу до 3 елементів.
- Навантаження на сервер — постійні запити до API (polling) можуть уповільнити сайт. Застосовуємо Server-Sent Events (SSE), що знижує навантаження в 10 разів порівняно з опитуванням кожні 5 секунд.
| Метод | Навантаження на сервер | Затримка | Складність |
|---|---|---|---|
| Polling (кожні 5с) | Висока | Середня | Низька |
| SSE | Низька | Низька | Середня |
| WebSocket | Низька | Мінімальна | Висока |
SSE — оптимальний вибір для лічильника онлайн і сповіщень, якщо не потрібен двосторонній зв'язок. Докладніше про SSE — у документації MDN.
Чому реальні дані важливіші за фейкові?
Соціальне підтвердження працює, лише якщо воно правдиве. За законодавством про рекламу хибні сповіщення можуть бути визнані порушенням. Крім того, користувачі стали уважнішими — вони можуть перевірити товар в іншому вікні. Згідно з опитуваннями, 92% споживачів довіряють рекомендаціям інших людей. Ми завжди вимагаємо доступ до реальних даних (наприклад, останні покупки з CRM) і генеруємо сповіщення лише на їх основі. Це підвищує довіру і не підводить під санкції. Реальні сповіщення з CRM дають у 4 рази більше переходів на картку товару порівняно з фейковими.
Як ми реалізуємо сповіщення без навантаження на бекенд?
Використовуємо SSE для лічильника онлайн і REST API для сповіщень про покупки з кешуванням на 10 секунд. У піковий час це дає 360 запитів замість 3600 при polling. Наше рішення в 3 рази швидше за готові плагіни за продуктивністю і не потребує постійних оновлень. Нижче — код компонента сповіщень, який ми використовуємо в продакшені.
// purchase-notifications.ts
interface PurchaseEvent {
customerName: string; // "Алексей из Москвы"
productName: string;
productUrl?: string;
timeAgo: string; // "2 минуты назад"
avatarUrl?: string;
}
interface NotificationConfig {
position?: 'bottom-left' | 'bottom-right';
displayMs?: number; // сколько висит уведомление
intervalMs?: number; // пауза между уведомлениями
maxQueue?: number; // макс. в очереди
}
export class PurchaseNotifier {
private queue: PurchaseEvent[] = [];
private isShowing = false;
private container: HTMLElement;
private config: Required<NotificationConfig>;
constructor(config: NotificationConfig = {}) {
this.config = {
position: 'bottom-left',
displayMs: 5000,
intervalMs: 8000,
maxQueue: 5,
...config,
};
this.container = this.createContainer();
document.body.appendChild(this.container);
}
private createContainer(): HTMLElement {
const el = document.createElement('div');
const pos = this.config.position;
el.style.cssText = `
position: fixed;
${pos === 'bottom-left' ? 'left: 20px' : 'right: 20px'};
bottom: 20px;
z-index: 9998;
pointer-events: none;
`;
return el;
}
push(events: PurchaseEvent[]) {
const toAdd = events.slice(0, this.config.maxQueue - this.queue.length);
this.queue.push(...toAdd);
if (!this.isShowing) this.showNext();
}
private async showNext() {
if (this.queue.length === 0) { this.isShowing = false; return; }
this.isShowing = true;
const event = this.queue.shift()!;
const toast = this.createToast(event);
this.container.appendChild(toast);
// анимация появления
requestAnimationFrame(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateY(0)';
});
await new Promise(r => setTimeout(r, this.config.displayMs));
// анимация исчезновения
toast.style.opacity = '0';
toast.style.transform = 'translateY(10px)';
await new Promise(r => setTimeout(r, 300));
toast.remove();
await new Promise(r => setTimeout(r, this.config.intervalMs));
this.showNext();
}
private createToast(event: PurchaseEvent): HTMLElement {
const toast = document.createElement('div');
toast.style.cssText = `
display: flex;
align-items: center;
gap: 12px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 12px 16px;
box-shadow: 0 4px 16px rgba(0,0,0,.1);
max-width: 300px;
pointer-events: auto;
cursor: default;
opacity: 0;
transform: translateY(16px);
transition: opacity .3s, transform .3s;
font-family: system-ui, sans-serif;
font-size: 13px;
`;
const avatar = event.avatarUrl
? `<img src="${event.avatarUrl}" alt="" width="36" height="36" style="border-radius:50%;flex-shrink:0">`
: `<div style="width:36px;height:36px;border-radius:50%;background:#dbeafe;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">🛒</div>`;
const productLink = event.productUrl
? `<a href="${event.productUrl}" style="color:#1d4ed8;text-decoration:none;font-weight:500">${event.productName}</a>`
: `<strong>${event.productName}</strong>`;
toast.innerHTML = `
${avatar}
<div>
<div style="color:#111827">
<strong>${event.customerName}</strong> купил(а) ${productLink}
</div>
<div style="color:#9ca3af;margin-top:2px">${event.timeAgo}</div>
</div>
<button style="margin-left:auto;background:none;border:none;cursor:pointer;color:#9ca3af;font-size:16px;padding:0;line-height:1" aria-label="Закрыть">×</button>
`;
toast.querySelector('button')?.addEventListener('click', () => {
toast.remove();
});
return toast;
}
destroy() {
this.container.remove();
}
}
// Загрузка реальных данных из API
async function loadRecentPurchases(productId?: string): Promise<PurchaseEvent[]> {
const url = new URL('/api/social-proof/purchases', location.origin);
if (productId) url.searchParams.set('product_id', productId);
url.searchParams.set('limit', '10');
const res = await fetch(url.toString());
const data: Array<{
buyer_city: string;
product_name: string;
product_url: string;
purchased_at: string;
}> = await res.json();
const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });
return data.map(item => {
const secondsAgo = (Date.now() - new Date(item.purchased_at).getTime()) / 1000;
let timeAgo: string;
if (secondsAgo < 3600) {
timeAgo = rtf.format(-Math.floor(secondsAgo / 60), 'minute');
} else if (secondsAgo < 86400) {
timeAgo = rtf.format(-Math.floor(secondsAgo / 3600), 'hour');
} else {
timeAgo = rtf.format(-Math.floor(secondsAgo / 86400), 'day');
}
return {
customerName: item.buyer_city,
productName: item.product_name,
productUrl: item.product_url,
timeAgo,
};
});
}
// Инициализация
const notifier = new PurchaseNotifier({ position: 'bottom-left', displayMs: 6000 });
loadRecentPurchases().then(events => notifier.push(events));
Лічильники та бейджі
Лічильник онлайн-користувачів
Реальний лічильник — через WebSocket або Server-Sent Events. Для простих випадків достатньо SSE:
// online-counter.ts
export function initOnlineCounter(selector: string) {
const el = document.querySelector(selector);
if (!el) return;
const sse = new EventSource('/api/online-count');
sse.addEventListener('count', (e: MessageEvent) => {
const count = parseInt(e.data, 10);
el.textContent = formatCount(count);
// пульсация при изменении
el.classList.remove('pulse');
void (el as HTMLElement).offsetWidth; // reflow trick
el.classList.add('pulse');
});
sse.addEventListener('error', () => {
setTimeout(() => initOnlineCounter(selector), 5000);
});
return () => sse.close();
}
function formatCount(n: number): string {
if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
return String(n);
}
// Серверная часть (Node.js/Express)
import { Router } from 'express';
const onlineCountRouter = Router();
const clients = new Set<NodeJS.WritableStream>();
// Обновляем счётчик из Redis или in-memory
setInterval(async () => {
const count = await redis.get('online_users_count') ?? '0';
const message = `event: count\ndata: ${count}\n\n`;
clients.forEach(client => {
try { client.write(message); }
catch { clients.delete(client); }
});
}, 5000);
onlineCountRouter.get('/', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders();
clients.add(res);
req.on('close', () => clients.delete(res));
});
Бейджі «X купили за 24 години»
Статичні або напівстатичні елементи, які можна кешувати:
// SalesBadge.tsx
interface SalesBadgeProps {
count: number;
period?: '24h' | '7d' | '30d';
threshold?: number; // не показывать, если меньше
}
const PERIOD_LABELS = {
'24h': 'за добу',
'7d': 'за тиждень',
'30d': 'за місяць',
};
function pluralize(n: number, forms: [string, string, string]): string {
const abs = Math.abs(n) % 100;
const mod = abs % 10;
if (abs > 10 && abs < 20) return forms[2];
if (mod === 1) return forms[0];
if (mod >= 2 && mod <= 4) return forms[1];
return forms[2];
}
export function SalesBadge({ count, period = '24h', threshold = 5 }: SalesBadgeProps) {
if (count < threshold) return null;
return (
<div className="inline-flex items-center gap-1.5 bg-orange-50 border border-orange-200 text-orange-700 text-xs font-medium px-2.5 py-1 rounded-full">
<span className="w-1.5 h-1.5 bg-orange-400 rounded-full animate-pulse" />
{count} {pluralize(count, ['продаж', 'продажі', 'продажів'])} {PERIOD_LABELS[period]}
</div>
);
}
Типові помилки та їх вирішення
| Помилка | Рішення |
|---|---|
| Використання випадкових даних | Підключіть реальні джерела (CRM, замовлення) |
| Занадто часта зміна сповіщень | Інтервал 8–15 секунд, черга не більше 5 |
| Ігнорування мобільної версії | Адаптивний дизайн, кнопка закриття |
| Відсутність можливості вимкнути | Cookie/localStorage для налаштування |
Докладніше про вибір протоколу
Для лічильника онлайн і сповіщень ми рекомендуємо SSE, якщо не потрібен двосторонній зв'язок. Якщо потрібна передача даних від клієнта до сервера (наприклад, повідомлення або синхронізація кошика), використовуйте WebSocket. У 90% випадків SSE достатньо, і він простіший у реалізації.Що входить у роботу
- Документація компонентів з прикладами інтеграції.
- Доступ до репозиторію з вихідним кодом (React/Vue/TS).
- Навчання команди (1 година онлайн).
- Підтримка протягом 1 місяця після деплою.
Процес роботи та строки
- Аналітика — аналіз даних про покупки, вибір тригерів, аудит поточних API.
- Проєктування — прототип у Figma з урахуванням брендбуку; узгодження частоти та позиціонування.
- Реалізація — написання компонентів (React/Vue/TS) та серверної частини (SSE/REST); інтеграція з CRM.
- Тестування — A/B-тест конверсії, навантажувальне тестування, UX-перевірка.
- Деплой — викатка на production, моніторинг продуктивності та помилок.
Впровадження під ключ займає від 4 до 7 робочих днів залежно від складності інтеграції та кількості елементів. Строк уточнюється після аналізу вашого сайту.
Чому обирають нас
Ми займаємося впровадженням Social Proof понад 5 років — реалізували 30+ проєктів для інтернет-магазинів, сервісів і лендінгів. Гарантуємо стабільну роботу компонентів та відсутність витоку даних. Маємо досвід інтеграції з популярними CMS (WordPress, Shopify) та кастомними рішеннями. Економія на серверних ресурсах при переході з polling на SSE може досягати 90%, а збільшення конверсії на 10–15% приносить додатковий виторг. Замовте впровадження Social Proof та отримайте зростання конверсії вже за тиждень. Зв'яжіться з нами для обговорення вашого проєкту.







