Подключил Яндекс.Метрику к лендингу. Счётчик инициализируется, скрипт грузится, в Метрике — тишина. window.ym в консоли — undefined.
Час потерял на очевидное. Делюсь, чтобы вы не теряли.
>> ПРОБЛЕМА: tag.js не создаёт window.ym
Официальная документация Метрики предлагает вставить кусок кода — сниппет с очередью. Он создаёт window.ym как функцию-очередь, складывает вызовы до загрузки скрипта, потом исполняет. Стандартная схема.
Но в Next.js App Router я загружал скрипт динамически:
const script = document.createElement("script");
script.src = "https://mc.yandex.ru/metrika/tag.js";
script.async = true;
document.head.appendChild(script);
script.onload = () => {
window.ym(107042392, "init", { clickmap: true });
// TypeError: window.ym is not a function
};tag.js загружается напрямую — без сниппета с очередью. Без очереди window.ym никто не создаёт. Скрипт просто регистрирует window.Ya.Metrika2 — конструктор счётчика. И всё.
>> РЕШЕНИЕ: Ya.Metrika2 instance API
Вместо ym() — создаём экземпляр счётчика напрямую и сохраняем в ref:
const counterRef = useRef<YaMetrikaCounter | null>(null);
script.onload = () => {
counterRef.current = new window.Ya.Metrika2({
id: 107042392,
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
});
// первый hit — текущая страница
counterRef.current.hit(window.location.pathname);
};Для отслеживания переходов между страницами (App Router меняет URL без перезагрузки):
useEffect(() => {
if (!counterRef.current) return;
const currentPath = searchParams?.toString()
? `${pathname}?${searchParams}`
: pathname;
if (currentPath !== lastPathRef.current) {
lastPathRef.current = currentPath;
counterRef.current.hit(currentPath);
}
}, [pathname, searchParams]);Без .hit() при смене маршрута Метрика видит только первую страницу визита — остальное считается одним сеансом.
>> ТИПЫ
window.Ya нигде не задекларирован — TypeScript будет ругаться. Добавляю интерфейсы:
interface YaMetrikaCounter {
hit: (url: string, options?: { referer?: string }) => void;
}
interface Ya {
Metrika2: new (config: {
id: number;
clickmap?: boolean;
trackLinks?: boolean;
accurateTrackBounce?: boolean;
webvisor?: boolean;
}) => YaMetrikaCounter;
}
declare global {
interface Window {
Ya?: Ya;
}
}Pro Tips: Как получить максимум
- Проверяй
typeof window.Ya?.Metrika2передnew— если скрипт заблокирован AdBlock, конструктора не будет, и без проверки получишь неперехваченный TypeError - Храни инстанс в ref, не в state — ре-рендер не нужен, нужна стабильная ссылка между эффектами
- Первый
.hit()вызывай сразу после init — иначе первая страница визита не фиксируется
Common Pitfalls: Где люди ошибаются
- Копируют сниппет с
window.ymв скрипт-тег — это работает в обычном HTML, но в App Router скрипт выполняется не в том месте и порядок гарантий нет - Не подписываются на смену маршрута — в App Router нет перезагрузки страницы, без
useEffectнаpathnameМетрика видит только первый визит - Инициализируют счётчик без
isInitializedRef— в React 18 Strict Mode эффекты запускаются дважды, получаешь два счётчика и задвоенные хиты