Open revolunet opened 1 year ago
any update?
What about this approach, maybe this could be used? https://sdorra.dev/posts/2022-11-11-next-with-fathom
Also looking into this. Anyone got it working?
I created such component but I used it just as a standalone one placed in the code like any other. Any help to make it align with current implementation and make it backwards-compatible will be appreciated.
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";
import { Suspense } from 'react'
import { getCookieConsentValue } from "react-cookie-consent";
declare global {
interface Window {
_paq: any;
}
}
const isExcludedUrl = (url: string, patterns: string[]): boolean => {
let excluded = false;
patterns.forEach((pattern) => {
if (new RegExp(pattern).exec(url) !== null) {
excluded = true;
}
});
return excluded;
};
interface InitSettings {
url?: string;
siteId?: string;
jsTrackerFile?: string;
phpTrackerFile?: string;
excludeUrlsPatterns?: string[];
onRouteChangeStart?: (path: string) => void;
onRouteChangeComplete?: (path: string) => void;
onInitialization?: () => void;
}
interface Dimensions {
dimension1?: string;
dimension2?: string;
dimension3?: string;
dimension4?: string;
dimension5?: string;
dimension6?: string;
dimension7?: string;
dimension8?: string;
dimension9?: string;
dimension10?: string;
}
// to push custom events
export function push(
args: (
| Dimensions
| number[]
| string[]
| number
| string
| null
| undefined
)[]
): void {
if (!window._paq) {
window._paq = [];
}
window._paq.push(args);
}
const startsWith = (str: string, needle: string) => {
return str.substring(0, needle.length) === needle;
};
function Tracker({
url,
siteId,
jsTrackerFile = "matomo.js",
phpTrackerFile = "matomo.php",
excludeUrlsPatterns = [],
onRouteChangeStart = undefined,
onRouteChangeComplete = undefined,
onInitialization = undefined,
}: InitSettings) {
const pathname = usePathname();
const searchParams = useSearchParams();
const [prevPath, setPrevPath] = useState(pathname);
useEffect(() => {
window._paq = window._paq !== null ? window._paq : [];
if (!url) {
console.warn("Matomo disabled, please provide matomo url");
return;
}
// order is important -_- so campaign are detected
const excludedUrl = typeof window !== "undefined" && isExcludedUrl(window.location.pathname, excludeUrlsPatterns);
if (onInitialization) onInitialization();
if (getCookieConsentValue("haczykowskaConsent")) {
push(["forgetUserOptOut"]);
push(["rememberCookieConsentGiven"]);
} else {
push(["optUserOut"]);
push(["forgetCookieConsentGiven"]);
push(["requireCookieConsent"]);
}
push(["enableHeartBeatTimer"]);
push(["disableQueueRequest"]);
push(["enableLinkTracking"]);
push(["setTrackerUrl", `${url}/${phpTrackerFile}`]);
push(["setSiteId", siteId]);
if (excludedUrl) {
if (typeof window !== "undefined") {
console.log(`matomo: exclude track ${window.location.pathname}`);
}
} else {
push(["trackPageView"]);
}
/**
* for initial loading we use the location.pathname
* as the first url visited.
* Once user navigate across the site,
* we rely on Router.pathname
*/
const scriptElement = document.createElement("script");
const refElement = document.getElementsByTagName("script")[0];
scriptElement.type = "text/javascript";
scriptElement.async = true;
scriptElement.defer = true;
scriptElement.src = `${url}/${jsTrackerFile}`;
if (refElement.parentNode) {
refElement.parentNode.insertBefore(scriptElement, refElement);
}
}, [])
useEffect(() => {
if (!pathname || getCookieConsentValue("haczykowskaConsent")) {
return;
}
if (!prevPath) {
return setPrevPath(pathname);
}
push(["setReferrerUrl", `${prevPath}`]);
push(["setCustomUrl", pathname]);
push(["deleteCustomVariables", "page"]);
setPrevPath(pathname);
if (onRouteChangeStart) onRouteChangeStart(pathname);
// In order to ensure that the page title had been updated,
// we delayed pushing the tracking to the next tick.
setTimeout(() => {
push(["setDocumentTitle", document.title]);
if (!!searchParams) {
push(["trackSiteSearch", searchParams.get("keyword") ?? ""]);
} else {
push(["trackPageView"]);
}
}, 0);
if (onRouteChangeComplete) onRouteChangeComplete(pathname);
}, [pathname, searchParams, prevPath, excludeUrlsPatterns, onRouteChangeComplete, onRouteChangeStart]);
return null;
}
const MatomoTracker = (props: InitSettings) => {
return (
<Suspense fallback={null}>
<Tracker {...props} />
</Suspense>
)
}
export default MatomoTracker
Thanks for the snippet. It does work, but it looks like it tracks page views very sporadically?
Maybe, I noticed that too. Eventually, I abandoned Matomo and implemented Google Tag Manager, so feel free to modify my snippet however you like, maybe you will make it work as it should be.
This is an attempt to set up Matomo for a simpler case, with no GDPR consent banner.
Might need some adjustments, I'll watch the Matomo events.
https://github.com/betagouv/reno/blob/master/utils/Matomo.tsx
Thanks laem, very useful, I used your code attempt, and it is tracking. However, and I might be wrong, it doesn't seem to track the different paths, I can only see the root path in Matomo.
I've pushed a custom URL to Matomo and now it's working. On line 26:
push(['setCustomUrl', pathName + searchParamsString]);
Thanks ! I can see different paths on my dashboard, but it may only be the initial page paths, not the subsequent ones. Thanks !
Thanks @laem ! your repo is is very useful 😁
Check the repo here https://github.com/betagouv/reno/blob/master/utils/Matomo.tsx, and on line 26 add: push(['setCustomUrl', pathName + searchParamsString]);
Thanks, done !
Implementation with prevention of double tracking on the first site visit.
'use client'
import { init, push } from '@socialgouv/matomo-next';
import { usePathname } from 'next/navigation';
import { useEffect, useRef } from 'react';
const MATOMO_URL = 'https://your-domain.matomo.cloud/';
const MATOMO_SITE_ID = '1';
export function MatomoAnalytics() {
const pathname = usePathname();
const isInitialLoad = useRef(true);
useEffect(() => {
init({ url: MATOMO_URL, siteId: MATOMO_SITE_ID });
return () => push(['HeatmapSessionRecording::disable']);
}, []);
useEffect(() => {
if (isInitialLoad.current) {
isInitialLoad.current = false;
} else {
if (pathname) {
push(['setCustomUrl', pathname]);
push(['trackPageView']);
}
}
}, [pathname])
return null
}
Looks like the new "app router" in NextJS@13 removed the
router.events
.A fix is required to handle this news navigation pattern
https://github.com/vercel/next.js/discussions/42016
some example implementation : https://github.com/SocialGouv/mda/pull/286