Алексей Баранов

Подключение счётчика Яндекс Метрики к Next.js приложению

Cover Image for Подключение счётчика Яндекс Метрики к Next.js приложению
Алексей Баранов
Алексей Баранов

Подключение счётчика Яндекс Метрики к Next.js приложению

Решил заняться аналитикой своего блога.

На первых порах мне нужно всего лишь отслеживать заходы на сайт и переходы по ссылкам между страницами сайта.

Для этих целей подходят счётчики от Google и Яндекса, но я порыве патриотических чувств решил остановиться на последнем.

Добавить счётчик несложно, но так как под капотом у нас статически сгенерированное Next.js/React приложение, то я столкнулся с парой нюансов, которые надо учесть.

В этой статье, я как раз о них и расскажу.

Ссылка на итоговый результат для тех кто спешит.

Итак, нам нужно:

Создание компонента

После быстрого поиска, я нашёл уже готовый пакет react-yandex-metrika, который реализует всё необходимое для моих задач.

Устанавливаем его:

npm i react-yandex-metrika

Далее нам необходимо создать компонент, назовём его YandexMetrikaContainer.

Так как счётчик должен быть Клиентским компонентом, то в файл с компонентом, перед всеми импортами, необходимо добавить директиву "use client".

"use client";

Далее создаём простой функциональный компонент, использующий YMInitializer из пакета react-yandex-metrika.

Прокидываем в него номер счётчика и другие необходимые нам настройки счётчика.

"use client";

import React from "react";
import { YMInitializer } from "react-yandex-metrika";

const YM_COUNTER_ID = 12345678; //Не настоящий :)

const YandexMetrikaContainer: React.FC = () => {
  return (
    <YMInitializer
      accounts={[YM_COUNTER_ID]}
      options={{
        defer: true,
        webvisor: true,
        clickmap: true,
        trackLinks: true,
        accurateTrackBounce: true,
      }}
      version="2"
    />
  );
};

export default YandexMetrikaContainer;

Но это ещё не всё, помимо загрузки счётчика, нам нужно отправить в него событие попадания на страницу.

import ym from "react-yandex-metrika";

const hit = (url: string) => {
  ym("hit", url);
};

useEffect(() => {
  hit(window.location.pathname + window.location.search);
}, []);

На этом бы можно было остановиться, но при переходе между страницами Next.js не рендерит весь Root Layout, поэтому событие будет вызвано только 1 раз.

Что бы исправить ситуацию, нам нужно подписаться на событие перехода между страницами.

Подписка на события перехода по страницам

Next.js Содержит в себе пакет Router, который позволяет подписаться на нужное нам событие.

Выглядит это следующим образом:

import Router from "next/router";

Router.events.on("routeChangeComplete", callback);

В итоге у нас должно получиться вот так:

import Router from "next/router";
import ym from "react-yandex-metrika";

const hit = (url: string) => {
  ym("hit", url);
};

useEffect(() => {
  hit(window.location.pathname + window.location.search);
  Router.events.on("routeChangeComplete", (url: string) => hit(url));
}, []);

Теперь настало время добавить компонент в Root Layout.

Добавление в Root Layout

Тут всё довольно просто, я решил добавить компонент после закрывающего тега .

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ru" className="scroll-smooth">
      <head>
        ...
      </head>
      <body className={inter.className}>
        ...
      </body>
      <YandexMetrikaContainer/>
    </html>
  );
}

Работа в Dev режиме

Также мне не нужно чтобы счётчик срабатывал когда я запускаю приложение в режиме отладки, поэтому неплохо бы добавить какой-то признак того что мы в режиме отладки

const analyticsEnabled = !!(process.env.NODE_ENV === "production");

В итоге использование компонента будет выглядеть следующим образом:

<YandexMetrikaContainer enabled={analyticsEnabled} />

Итоговый результат

Итоговый код компонента YandexMetrikaContainer

"use client";

import Router from "next/router";
import React, { useCallback, useEffect } from "react";
import ym, { YMInitializer } from "react-yandex-metrika";

type Props = {
  enabled: boolean;
};

const YM_COUNTER_ID = 12345678; //Не настоящий :)

const YandexMetrikaContainer: React.FC<Props> = ({ enabled }) => {
  const hit = useCallback(
    (url: string) => {
      if (enabled) {
        ym("hit", url);
      } else {
        console.log(`%c[YandexMetrika](HIT)`, `color: orange`, url);
      }
    },
    [enabled],
  );

  useEffect(() => {
    hit(window.location.pathname + window.location.search);
    Router.events.on("routeChangeComplete", (url: string) => hit(url));
  }, [hit]);

  if (!enabled) return null;

  return (
    <YMInitializer
      accounts={[YM_COUNTER_ID]}
      options={{
        defer: true,
        webvisor: true,
        clickmap: true,
        trackLinks: true,
        accurateTrackBounce: true,
      }}
      version="2"
    />
  );
};

export default YandexMetrikaContainer;

Итоговый код компонента Root Layout


const analyticsEnabled = !!(process.env.NODE_ENV === "production");

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ru" className="scroll-smooth">
      <head>
        ...
      </head>
      <body className={inter.className}>
        ...
      </body>
      <YandexMetrikaContainer enabled={analyticsEnabled} />
    </html>
  );
}

На этом всё, спасибо за внимание! 🎉

Подписывайтесь на мой Youtube канал и на Telegram 🙂