Как вставить счётчики Yandex Metrika на сайт с Next.js

Балашиха Нужен мастер для ремонта Как вставить счётчики Yandex Metrika на сайт с Next.js
Время на чтение:
5 минут
Дата публикации:
27 декабря 2023 г.
Просмотры:
114

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

Самое простое, что можно сделать вначале это установить скрипт Yandex Метрики на свой сайт.

В случае использования фреймворка Next.js нужно определиться каким образом интегрировать html-код Яндекс метрики в разметку React.js.

При этом не хотелось бы помещать шаблон скрипта Яндекс метрики в репозиторий кода.

На своём сайте я поместил все скрипты аналитики и счётчиков (в частности счётчик Mail.ru) в один простой html файл.

html
<!-- Top.Mail.Ru counter -->
<script type="text/javascript">
var _tmr = window._tmr || (window._tmr = []);
_tmr.push({ id: "###", type: "pageView", start: new Date().getTime() });
(function (d, w, id) {
if (d.getElementById(id)) return;
var ts = d.createElement("script");
ts.type = "text/javascript";
ts.async = true;
ts.id = id;
ts.src = "https://top-fwz1.mail.ru/js/code.js";
var f = function () {
var s = d.getElementsByTagName("script")[0];
s.parentNode.insertBefore(ts, s);
};
if (w.opera == "[object Opera]") {
d.addEventListener("DOMContentLoaded", f, false);
} else {
f();
}
})(document, window, "tmr-code");
</script>
<noscript
><div>
<img
src="https://top-fwz1.mail.ru/counter?id=###;js=na"
style="position: absolute; left: -9999px"
alt="Top.Mail.Ru"
/></div
></noscript>
<!-- /Top.Mail.Ru counter -->
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(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("###", "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
});
</script>
<noscript
><div>
<img
src="https://mc.yandex.ru/watch/###"
style="position: absolute; left: -9999px"
alt=""
/></div
></noscript>
<!-- /Yandex.Metrika counter -->
<!--LiveInternet counter-->
<script>
new Image().src =
"https://counter.yadro.ru/hit?r" +
escape(document.referrer) +
(typeof screen == "undefined"
? ""
: ";s" +
screen.width +
"*" +
screen.height +
"*" +
(screen.colorDepth ? screen.colorDepth : screen.pixelDepth)) +
";u" +
escape(document.URL) +
";h" +
escape(document.title.substring(0, 150)) +
";" +
Math.random();
</script>
<!--/LiveInternet-->
<!-- Top.Mail.Ru logo -->
<a href="https://top-fwz1.mail.ru/jump?from=###">
<img
src="https://top-fwz1.mail.ru/counter?id=###;t=466;l=1"
height="31"
width="88"
alt="Top.Mail.Ru"
style="border: 0"
/></a>
<!-- /Top.Mail.Ru logo -->
<!--LiveInternet logo-->
<a href="https://www.liveinternet.ru/click" target="_blank"
><img
src="https://counter.yadro.ru/logo?52.5"
title="LiveInternet: показано число просмотров и посетителей за 24 часа"
alt=""
style="border: 0"
width="88"
height="31"
/></a>
<!--/LiveInternet-->

Теперь возникает вопрос, как встроить этот код в приложение Next.js.

Удобнее всего было бы как-то запихнуть всё это в переменную окружения process.env.

Так как этот текст html содержит переносы строк, я не стал использовать стандартный dotenv плагин webpack.

Я поместил в файл конфигурации next.config.js такой код:

javascript
/** @type {import('next').NextConfig} */
const fs = require("fs");
module.exports = {
webpack(config, { buildId, dev, isServer, defaultLoaders, webpack }) {
config.plugins.push(
new webpack.DefinePlugin({
"process.env.metrika_counters": JSON.stringify(
fs.readFileSync("webpack-define-plugin/metrika_counters.html", {
enc: "utf-8",
}),
),
}),
);
return config;
},
};

Webpack Define Plugin просто заменяет указанную в двойных кавычках переменную process.env.metrika_counters по её имени во всём проекте на значение, которое мы указали: читаем html файл с кодом аналитики. Вызов JSON.stringify нужен для того, чтобы завернуть строку в кавычки, а то сам webpack-define-plugin этого не понимает (будет ошибка компиляции).

Я создал папку "webpack-define-plugin" в корне проекта и добавил её в файл .gitignore как "/webpack-define-plugin/**".

Теперь чтобы интегрировать html-код в структуру файла React jsx (tsx для typescript) нужно придумать что-то вроде парсера html кода в разметку React. В случае Next.js для тэгов script, img и a (ссылка) нужно использовать предоставляемые разработчиками Next.js компоненты: Script (next/script), Image (next/image), Link (next/link).

Я нашёл хороший пакет npm:

bash
npm install html-react-parser

Это компонент, который принимает на вход строку с html-кодом и парсит её генерируя компоненты React.

Вот мой компонент для скриптов аналитики:

tsx
const options = {
trim: true,
replace: (in_domNode: DOMNode) => {
if (in_domNode.type === "text") {
const domNode: Text = in_domNode as Text;
return domNode;
} else if (in_domNode.type === "style") {
return <></>;
} else if (in_domNode.type === "script") {
const domNode: Element = in_domNode as Element;
const { attribs, children } = domNode;
const convertedProps = attributesToProps(attribs);
const Children: string | JSX.Element | JSX.Element[] | undefined =
children && <>{domToReact(children, options)}</>;
return (
<Script
{...(convertedProps as any)}
id={`inline-script-id-${crc32(Children?.props?.children || "")}`}
strategy="afterInteractive"
>
{`${Children?.props?.children || ""}`}
</Script>
);
} else if (in_domNode.type === "tag" && (in_domNode as Element).name) {
const domNode: Element = in_domNode as Element;
const { attribs, children } = domNode;
const convertedProps = attributesToProps(attribs);
const Children: string | JSX.Element | JSX.Element[] | undefined =
children && <>{domToReact(children, options)}</>;
if (typeof convertedProps.children !== "undefined") {
delete convertedProps.children;
}
if (domNode.name === "noscript") {
return domNode;
} else if (domNode.name === "a" && convertedProps.href) {
return (
<Link {...(convertedProps as any)} target="_blank">
{Children}
</Link>
);
} else if (domNode.name === "img") {
// case "img":
const src = convertedProps.src || "";
if (!src) {
return null;
}
const width = parseInt(convertedProps.width || "1") || 1;
delete convertedProps.width;
const height = parseInt(convertedProps.height || "1") || 1;
delete convertedProps.height;
const alt = convertedProps.alt || "";
return (
<Image
{...(convertedProps as any)}
src={src}
alt={alt}
width={width}
height={height}
unoptimized
loading="lazy"
/>
);
} else {
const styleSX = convertedProps.style || undefined;
if (typeof convertedProps.style !== "undefined") {
delete (convertedProps as any).style;
}
return (
<Box
component={domNode.name}
{...(convertedProps as any)}
sx={{ ...styleSX }}
>
{Children}
</Box>
);
}
} else {
return in_domNode;
}
},
};
export default memo<Props>(function MetrikaScriptsParser({
htmlString,
}: Props) {
if (htmlString) {
return <>{parse(htmlString, options)}</>;
} else {
return null;
}
});

Метод replace объекта options отвечает за создание React компонентов для каждого скрипта или изображения.

Используется специальный компонент Script из фрэймворка Next.js:

tsx
import Script from "next/script";

Он принимает тело скрипта аналитики в виде строки внутрь тега (children).

Этому компоненту требуется уникальный атрибут id (идентификатор). Я его делаю уникальным просто вычисляя контрольную сумму crc32 от строки тела скрипта.

Также для изображений счётчиков (я использую счётчики-картинки mail.ru и liveinternet) используется компонент Image из next/image. Он требует обязательно указанных атрибутов width и height (ширина и длина изображения). У него обязательно нужно указать атрибут unoptimized, чтобы next.js не изменял изображение.

Звоните 8 (926) 212-12-55

Консультация компьютерного мастера в Балашихе

всегда бесплатна!

Контакты
Телефон:
8 (926) 212-12-55
Почта:
vk.com/bal_mast
На карте:
Балашиха,
шоссе Энтузиастов,
7