Пройдя эту статью, вы будете отправлять в КО действия пользователя внутри приложения — открыл отчёт, оформил подписку, дошёл до третьего шага мастера — и увидите их в отчёте разложенными по источникам и кампаниям. На страницах при этом уже должен стоять счётчик: подключение сайта.
Что счётчик делает сам
В одностраничном приложении счётчик отслеживает смену адреса и пишет просмотр на каждый переход между экранами. Визиты, источники, кампании, устройство и посетитель приезжают оттуда же. Настраивать это не нужно, и дублировать своим кодом не нужно тем более: подписка на роутер с собственным «просмотр страницы» удваивает просмотры, а не подстраховывает.
Своим кодом отправляется только то, что счётчик увидеть не может, — действия внутри приложения. Событие ложится на визит, и вопрос «сколько раз нажали» превращается в «сколько нажали из платного трафика и во что это превратилось».
Одно место всё же стоит настроить — фильтр параметров адреса, чтобы один экран не
размножился в отчёте на десяток строк из-за ?tab=2&_r=17. Объявляется до загрузки
счётчика:
var ko_options = {
modules: { visit: {
url_tracking_hash_router: false, // true, если маршруты через #/
url_tracking_query_patterns: ['*', '!utm_*', '!_*'],
} }
};
Что означают эти ключи — Вызовы счётчика.
Поставьте обёртку
Заведите одну тонкую обёртку и зовите только её. Прямой вызов счётчика из компонента — способ однажды отправить событие мимо словаря и в неправильной форме.
В Angular это сервис, в React — модуль; суть одна.
const PROD_HOST = 'app.example.com';
const COUNTER_SRC = 'https://cdn.kodata.pro/site/?hash=ВАШ_HASH';
const enabled = window.location.hostname === PROD_HOST;
export function initCounter(): void {
if (!enabled) return;
const script = document.createElement('script');
script.src = COUNTER_SRC;
script.async = true;
document.head.appendChild(script);
}
export function track(event: string, data: Record<string, string | number | boolean> = {}): void {
call('track', [event, { data }]);
}
export function setContext(data: Record<string, string | number | boolean>): void {
call('setTrackData', [data, true]);
}
function call(method: 'track' | 'setTrackData', args: any[]): void {
if (!enabled) return;
const visit = (window as any).ko?.visit;
if (visit && typeof visit[method] === 'function') {
visit[method].apply(visit, args);
return;
}
(window as any).koLayer = (window as any).koLayer || [];
(window as any).koLayer.push(['visit', method, args]);
}
Подставьте свой домен в PROD_HOST и свой ключ в COUNTER_SRC — больше в этом блоке
менять нечего. Форма вызова, устройство очереди koLayer и второй аргумент setTrackData
разобраны в Вызовах счётчика.
Гейт по домену обязателен. Без него в проекте появятся «сайты» вроде localhost и
192.168.0.5, а числа использования наполовину будут состоять из работы разработчиков.
В обёртке выше он гасит и сам счётчик, и все вызовы.
Отправьте первое событие
Возьмите действие, факт которого подтверждает сервер, и поставьте вызов в успешную ветку ответа:
track('report_exported', { rows: rowCount, format: 'xlsx' });
Имя события — латиница, нижний регистр, прошедшее время; различия между случаями идут в данные, а не в имя. Правила целиком — Словарь событий.
Поля, которые не меняются от события к событию, ставьте контекстом там, где факт стал известен, — тогда их не придётся повторять в каждом вызове:
setContext({ user_id: user.id, plan: user.plan }); // после входа
setContext({ workspace_id: workspaceId }); // при переключении рабочей области
Слои живут в памяти страницы и после перезагрузки проставляются заново.
Время на экране меряйте сами
Счётчик отправляет время в событии закрытия страницы, но для вопроса «сколько минут смотрели вот этот экран» оно не годится: в том событии нет адреса, а время активности копится по странице целиком. В обычном сайте это одно и то же, в SPA — нет.
Поэтому экран, время на котором нужно померить, меряет себя сам:
let openedKey = '', openedAt = 0;
function openScreen(code: string) {
if (code === openedKey) return; // подписки роутера срабатывают чаще, чем меняется экран
closeScreen();
openedKey = code; openedAt = Date.now();
track('screen_opened', { screen: code });
}
function closeScreen() {
if (!openedKey) return;
track('screen_closed', { screen: openedKey, duration_ms: Date.now() - openedAt });
openedKey = '';
}
Накройте три случая, иначе половина «закрытий» не отправится: смена экрана внутри
приложения, сворачивание вкладки (visibilitychange — если считаете активное время
отдельно) и уход со страницы (pagehide; beforeunload на мобильных не срабатывает).
Проверка
Откройте живое приложение на боевом домене, откройте вкладку Network и сделайте
действие, на которое повесили вызов. Признак успеха — запрос visit.php?type=event; в его
теле имя вашего события и поля внутри data.
Если запроса нет, добавьте к адресу ?ko_debug и повторите: счётчик начнёт писать в
консоль, что с ним происходит.
Числа в отчёте появятся после того, как вы соберёте из события метрику, — это следующий раздел. Но факт отправки виден здесь и сразу, без ожидания.
Соберите из события метрику
- В разделе Метрики проекта создайте метрику: свой ключ (например
appReportExported) и формула{sites_events:visitor:report_exported}— имя вашего события в третьей части. - Долю и конверсию считайте формулой по ключам метрик:
{appExports}/{appReports}. - Уникальные люди, а не нажатия:
{sites_events:visitor:report_exported[:value=visitors_id][:aggregate=uniq]}. - Разрез по данным события — измерением
field:data.productв секции Разрез вашего отчёта. Отдельная метрика на каждое значение не нужна. - Если событие несёт деньги, передавайте сумму полем
incomeрядом сdata, а не внутри него, — тогда она складывается как деньги.
Если не сходится
Метрика показывает 0, хотя события идут. Почти всегда двоеточие в имени события.
Проверьте имя: только латиница, цифры и _.
В разрезе одна пустая строка. Данные события передали плоским объектом. Рабочая форма —
{ data: { … } }, её обеспечивает обёртка; прямой вызов счётчика мимо обёртки её теряет.
События вообще не появляются. Счётчик не загрузился (проверьте в чистом профиле, без блокировщика) или вызов случился на домене, который вы сами и отключили гейтом.
Событий вдвое больше, чем действий. Вызов стоит на отрисовке, а не на факте: экран перерисовался — событие ушло снова.
Поле в отчёте не то, что передали. Ключ есть и в контексте, и в данных события — побеждает контекст. Один ключ живёт где-то одном.
Числа выглядят слишком хорошо. Проверьте, не попадает ли в проект ваша собственная разработка: в отчёте это видно разрезом по домену.
Что рядом
- Вызовы счётчика — форма вызова, конверт, очередь, опции
- Словарь событий — как называть и где ставить вызов
- Свои события с обычного сайта — если страницы перезагружаются
- Свои события со статического сайта — Astro, Hugo, Next, Eleventy