Подключение счетчика Яндекс.Метрики в Next.JS

24 июня 2023 г.
5 минут823 слова

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

Код счетчика Яндекс Метрики для NextJS

      <Script id="metrika-counter" strategy="afterInteractive">
      {`(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
        m[i].l=1*new Date();
        for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
        k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
        (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
 
        ym(XXXXXXXX, "init", {
              defer: true,
              clickmap:true,
              trackLinks:true,
              accurateTrackBounce:true,
              webvisor:true
        });`
      }
    </Script>

Вместо XXXXXXXX вставляете свой id счетчика.

Используя Pages Router

В случае с использованием Pages Router, я вставляю счетчик яндекс метрики в _document.tsx.

src\pages\_document.tsx

  import {Html, Head, Main, NextScript} from 'next/document'
  import Script from 'next/script';
 
  export default function Document() {
    return (
      <Html lang="ru">
        <Head/>
        <body>
        <Main/>
        <NextScript/>
        <Script id="metrika-counter" strategy="afterInteractive">
          {`(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
            m[i].l=1*new Date();
            for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
            k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
            (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
 
            ym(XXXXXXXX, "init", {
                  defer: true,
                  clickmap:true,
                  trackLinks:true,
                  accurateTrackBounce:true,
                  webvisor:true
            });`
          }
        </Script>
        </body>
      </Html>
    )
  }

У меня один общий макет <Layout /> для всего сайта, поэтому я создал Custom App и обернул его этим макетом.

src\pages\_app.tsx

import '@/styles/utils/normalize.css'
import '@/styles/_variables.scss'
import '@/styles/globals.scss'
import type {AppProps} from 'next/app'
import { Raleway, Roboto } from "next/font/google";
import Layout from "@/components/Layout/Layout";
import {DarkModeProvider} from "@/context/darkModeContext";
import { IconContext } from 'react-icons';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
// @ts-ignore
import NProgress from 'nprogress';
import "nprogress/nprogress.css";
import SEO from "@/components/SEO/SEO";
 
const raleway = Raleway({
style: ['normal'],
subsets: ['cyrillic', 'latin'],
fallback: ['open-sans', 'system-ui', 'arial']
});
const roboto = Roboto({
weight: ['400', '500', '700'],
style: ['normal', 'italic'],
subsets: ['latin', 'cyrillic'],
fallback: ['open-sans', 'system-ui', 'arial']
});
 
/_ Progressbar Configurations _/
NProgress.configure({
easing: "ease",
speed: 500,
showSpinner: false,
});
 
export default function App({Component, pageProps}: AppProps) {
 
const router = useRouter();
 
useEffect(() => {
const start = () => NProgress.start();
const end = () => NProgress.done();
 
    router.events.on("routeChangeStart", start);
    router.events.on("routeChangeComplete", end);
    router.events.on("routeChangeError", end);
 
    return () => {
      router.events.off("routeChangeStart", start);
      router.events.off("routeChangeComplete", end);
      router.events.off("routeChangeError", end);
    }
 
}, [router.events])
 
return (
 
<>
<style jsx global>{`
html {
font-family: ${roboto.style.fontFamily};
}
 
              h1, h2, h3, h4, h5, h6 {
                font-family: ${raleway.style.fontFamily};
                margin-bottom: 0.5em;
              }
            `}</style>
 
      <DarkModeProvider>
        <IconContext.Provider value={{ color: 'var(--icons-color)' }}>
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </ IconContext.Provider>
      </DarkModeProvider>
    </>
 
)
}

Подробнее про единый Layout и Custom App в документации: https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts

src\components\Layout\Layout.tsx

import { ReactNode, useEffect, useState } from 'react';
import dynamic from 'next/dynamic';
import Header from '@/components/Header/Header';
import Footer from '../Footer/Footer';
import ScrollProgressBar from '@/components/ScrollProgressBar/ScrollProgressBar';
import { YandexMetrika } from '../YandexMetrika/YandexMetrika';
import SEO from '../SEO/SEO';
 
const Cursor = dynamic(() => import('../Cursor/Cursor'), { ssr: false });
const ScrollTopButton = dynamic(() => import('../../components/ScrollTopButton/ScrollTopButton'), { ssr: false });
 
type LayoutProps = {
children?: ReactNode
}
 
export default function Layout({ children }: LayoutProps) {
 
return (
 
<>
  <SEO />
  <YandexMetrika />
  <Cursor />
  <Header />
  <main className="main-content-pt">{children}</main>
  <Footer />
  <ScrollTopButton />
  <ScrollProgressBar />
</>
); }

В макет вставляю компонент <YandexMetrika />, который будет отправлять данные в метрику при изменении URL-адреса или параметров в нем.

src\components\YandexMetrika\YandexMetrika.jsx

'use client'
 
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
 
export function YandexMetrika() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
useEffect(() => {
const url = `${pathname}?${searchParams}`
ym(XXXXXXXX, 'hit', url);
 
}, [pathname, searchParams])
 
return null
}

Используя App Router

В случае с использованием App Router, вставляем счетчик яндекс метрики в файл layout.tsx перед закрывающим тегом body.

app\layout.tsx

import { DarkModeProvider } from '@/context/darkModeContext';
import './globals.css';
import { Header } from '@/components/Header/Header';
import { Suspense } from 'react';
import Footer from '@/components/Footer/Footer';
import NextTopLoader from 'nextjs-toploader';
import YandexMetrika from '@/components/YandexMetrika/YandexMetrika';
import Script from 'next/script';
 
export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <DarkModeProvider>
      <html lang="ru">
        <body>
          <Script id="metrika-counter" strategy="afterInteractive">
            {`(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
              m[i].l=1*new Date();
              for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
              k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
              (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
 
              ym(XXXXXXXX, "init", {
                    defer: true,
                    clickmap:true,
                    trackLinks:true,
                    accurateTrackBounce:true,
                    webvisor:true
              });`
            }
          </Script>
          <YandexMetrika />
          <NextTopLoader height={4} />
          <Header />
          <main>{children}</main>
          <Footer />
        </body>
      </html>
    </DarkModeProvider>
  );
}

Туда же импортируем компонент, который будет отправлять данные в метрику при изменении URL-адреса или параметров в нем и добавляем в наш layout.

components\YandexMetrika\YandexMetrika.jsx

    'use client'
 
      import { useEffect } from 'react'
      import { usePathname, useSearchParams } from 'next/navigation'
 
      export default function YandexMetrika() {
        const pathname = usePathname()
        const searchParams = useSearchParams()
 
        useEffect(() => {
          const url = `${pathname}?${searchParams}`
          ym(XXXXXXXX, 'hit', url);
 
        }, [pathname, searchParams])
 
        return null
      }