Next.js нужен тег в одном месте, и дальше он вас не трогает: переходы на стороне клиента считают себя сами, поэтому подписываться на события маршрутизатора не надо и вызывать при смене маршрута нечего.

Сначала возьмите сниппет в своей панели. Выглядит он так, с вашим собственным идентификатором сайта в пути:

<script src="https://t.skomi.com/s/your-site-id.js" defer></script>

App Router

В app/layout.tsx, внутри <body>:

import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ru">
      <body>
        {children}
        <Script
          src="https://t.skomi.com/s/your-site-id.js"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

afterInteractive стоит по умолчанию и здесь правилен: страница должна сначала догрузиться, и ничто на ней аналитику не ждёт.

Pages Router

Тот же тег в pages/_app.tsx:

import Script from "next/script";
import type { AppProps } from "next/app";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Component {...pageProps} />
      <Script
        src="https://t.skomi.com/s/your-site-id.js"
        strategy="afterInteractive"
      />
    </>
  );
}

Именно _app, а не _document, чтобы он переживал переход на стороне клиента так же, как остальное ваше приложение.

Переходы на стороне клиента уже посчитаны

Это та часть, которую все ожидают написать, и писать здесь нечего.

Скрипт Skomi оборачивает history.pushState и history.replaceState и слушает popstate, так что каждая смена маршрута в Next — это просмотр страницы, независимо от того, нажал ли посетитель <Link>, вернулся назад или вы сами вызвали router.push.

Две детали стоит знать, потому что именно они выглядят ошибками:

  • Переход, не меняющий адрес, не считается. Замена адреса на тот же самый (что иногда делает фильтр или обновление позиции прокрутки) игнорируется, а не считается вторым просмотром.
  • Покидаемая страница закрывается первой. Её время на странице и глубина прокрутки сбрасываются до открытия нового просмотра, поэтому одностраничное приложение отчитывается о внимании по маршрутам, а не одним числом на всю сессию.

Не используйте strategy="worker"

next/script предлагает стратегию worker, переносящую сторонний скрипт в веб-воркер через Partytown. Для скриптов, которые только шлют события, это хорошая идея, а этот она ломает начисто: у воркера нет document, то есть страницы, которую можно прочитать, там нет.

Всему, что делает Skomi, нужна настоящая страница: тепловые карты и записи сессий — это её изображение, виджет отзывов должен на ней появиться, да и обычной аналитике нужен адрес, на который посетитель действительно смотрит. Берите afterInteractive.

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

Если у вас есть политика безопасности содержимого

Next.js упрощает добавление строгой CSP, и строгая CSP будет это блокировать, пока вы ей не скажете обратного. Скрипт и данные, которые он шлёт, используют один хост:

script-src  https://t.skomi.com
connect-src https://t.skomi.com

Если ваша политика использует nonce, примените его к этому тегу так же, как к своим.

В разработке ничего не приходит

localhost — не тот домен, который вы зарегистрировали, поэтому обращения с него отклоняет проверка источника. Это намеренно: вашей собственной работы в ваших числах быть не должно, и это значит, что npm run dev не то место, где подтверждают установку. Выложите на настоящий домен или его поддомен и смотрите там.

Проверьте, что работает

Загрузите страницу на настоящем домене, перейдите по ссылке на другой маршрут и посмотрите в свою панель: вы должны увидеть два просмотра страниц, а не один. Если ничего не пришло, смотрите что проверить, когда сниппет не работает.