Аналитика кликов
Отслеживайте, по каким результатам пользователи действительно кликают и какие запросы приводят к конверсиям — данные поступают в тюнер релевантности AACsearch и ваши собственные дашборды воронки.
Релевантность поиска настолько хороша, насколько хорош сигнал, который вы в него подаёте. AACsearch принимает события через POST /api/events/track для кликабельности (CTR), конверсий и запросов с нулевым результатом. Этот рецепт показывает, как подключить их, не замедляя клик.
Что вы отслеживаете
| Событие | Срабатывает, когда | Зачем |
|---|---|---|
search_query | Поиск вернул результаты | Объём запросов, топ запросов |
zero_results | Поиск вернул found: 0 | Поиск запросов, требующих синонимов или контента |
result_click | Пользователь кликает по результату | CTR по запросу, по позиции |
conversion | Пользователь достигает цели (добавление в корзину, покупка) | Связь дохода с исходным запросом |
filter_used | Пользователь переключает фасет | Наиболее используемые фильтры; приоритизация UI |
Тюнер релевантности использует result_click и conversion для понижения результатов, по которым кликают, но не конвертируются, и для повышения результатов, которые конвертируются выше базового уровня CTR.
Отправка события
async function trackEvent(event: string, properties: Record<string, unknown>) {
await fetch("/api/events/track", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
event,
properties: {
...properties,
timestamp: new Date().toISOString(),
sessionId: getOrCreateSessionId(),
},
}),
// keepalive позволяет запросу пережить навигацию по странице
keepalive: true,
});
}Эндпоинт принимает запрос и немедленно возвращает 202 — события помещаются в очередь, а не обрабатываются синхронно.
Отслеживание кликов с queryId
Каждый ответ поиска содержит queryId, который вы должны прикреплять к последующим кликам, чтобы AACsearch мог связать клик с исходным поиском.
const result = await client.search({ q: "shoes", queryBy: "title" });
// result.queryId: "qry_abc123..."
// Отображение результатов
result.hits.forEach((hit, position) => {
renderCard({
product: hit.document,
onClick: () => {
trackEvent("result_click", {
queryId: result.queryId,
query: "shoes",
documentId: hit.document.id,
position, // 0-индексированная
indexSlug: "products",
});
},
});
});Поле position критически важно — без него невозможно вычислить коррекцию смещения позиции клика.
Использование navigator.sendBeacon для SPA-навигации
Если клик вызывает переход на другую страницу (наиболее частый случай), стандартный fetch может быть отменён браузером до отправки. Три надёжных варианта:
function trackClickThenNavigate(url: string, payload: object) {
if ("sendBeacon" in navigator) {
navigator.sendBeacon("/api/events/track", JSON.stringify(payload));
} else {
// Запасной вариант: fetch с keepalive (отправил и забыл)
fetch("/api/events/track", {
method: "POST",
body: JSON.stringify(payload),
keepalive: true,
});
}
window.location.href = url;
}sendBeacon работает по принципу «отправил и забыл» и переживает выгрузку страницы. Используйте его для отслеживания кликов; используйте fetch с keepalive для событий, тело которых слишком велико для sendBeacon (ограничение ~64 КБ).
Отслеживание конверсий
Конверсия отправляется страницей, которая завершает цель пользователя — успешное оформление заказа, добавление в корзину, регистрация и т.д. Передавайте исходный queryId через навигацию:
// На странице результатов поиска
<Link href={`/products/${id}?qid=${result.queryId}`}>...</Link>;
// На странице товара
const queryId = useSearchParams().get("qid");
function onAddToCart() {
trackEvent("conversion", {
queryId,
documentId: id,
conversionType: "add_to_cart",
value: product.price,
currency: "USD",
});
}Для многошаговых воронок (поиск → товар → корзина → оформление), сохраняйте queryId в sessionStorage, чтобы он пережил все переходы.
Серверный рендеринг
Если ваша страница поиска рендерится на сервере, вы не можете отправить result_click с сервера (вы не знаете, по какому результату кликнет пользователь). Вместо этого:
- Отрисуйте
data-qid={result.queryId}иdata-pos={i}на каждой ссылке результата. - Подключите обработчик клика в небольшом клиентском скрипте, который читает эти атрибуты и отправляет событие.
<a
href={`/products/${id}`}
data-qid={result.queryId}
data-pos={i}
className="result-link"
>
{title}
</a>;
// Один глобальный обработчик, монтируемый один раз
document.addEventListener("click", (e) => {
const target = (e.target as HTMLElement).closest("a.result-link");
if (!target) return;
const qid = target.getAttribute("data-qid");
const pos = Number(target.getAttribute("data-pos"));
const docId = target.getAttribute("href")?.split("/").pop();
navigator.sendBeacon(
"/api/events/track",
JSON.stringify({
event: "result_click",
properties: { queryId: qid, position: pos, documentId: docId },
}),
);
});Конфиденциальность
События принимают произвольные properties. Не помещайте туда персональные данные (email, телефон, полные имена) — они хранятся для анализа релевантности, а не для профилирования пользователей. В частности:
- ✅
userId: "user_abc"(непрозрачный внутренний ID) - ✅
country: "DE" - ❌
email: "..." - ❌
address: "..."
Если вашей downstream-аналитике нужны персональные данные, храните их отдельно и соединяйте по userId.
Приём событий ограничен на уровне организации до 1000 событий/сек. Для очень
высоких объёмов используйте пакетную отправку через эндпоинт events:batch
(макс. 1000 событий за вызов).
Просмотр данных
Откройте Поиск → Аналитика → «Топ запросов» и «Топ запросов с нулевым результатом». Тюнер релевантности начинает учитывать сигналы примерно через 24 часа данных; для свежих дашбордов смотрите Поиск → Аналитика → «Последние события» (задержка 5 минут).
Св
язанные страницы
Бесконечная прокрутка
Пагинация с подгрузкой через `useInfiniteQuery` TanStack Query и sentinel на IntersectionObserver — один round-trip на скролл, без бесконечных спиннеров.
Рекомендации при отсутствии результатов
Когда `found: 0`, не показывайте пустую страницу. Покажите подборку бестселлеров, подсказки «возможно, вы имели в виду» и поисковую рекомендацию, которая восстанавливает поток пользователя.