Skip to content

SocialGouv/matomo-next

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Matomo Next

Matomo analytics for Next.js applications

Github Master CI Build Status License: Apache-2.0 GitHub release (latest SemVer) Npm version codecov





Features

  • âś… Basic SPA Matomo setup
  • âś… Supports Next.js Pages Router (automatic tracking with next/router events)
  • âś… Supports Next.js App Router (tracking with usePathname and useSearchParams)
  • âś… Tracks route changes and page views
  • âś… Tracks search queries on /recherche and /search routes
  • âś… Excludes URLs based on patterns
  • âś… GDPR compliant (optional cookie-less tracking)
  • âś… Custom event tracking
  • âś… TypeScript support

Usage

Pages Router

Add the trackPagesRouter call in your _app.js:

import React, { useEffect } from "react";
import App from "next/app";

import { trackPagesRouter } from "@socialgouv/matomo-next";

const MATOMO_URL = process.env.NEXT_PUBLIC_MATOMO_URL;
const MATOMO_SITE_ID = process.env.NEXT_PUBLIC_MATOMO_SITE_ID;

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    trackPagesRouter({ url: MATOMO_URL, siteId: MATOMO_SITE_ID });
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;

Will track routes changes by default.

App Router

For Next.js App Router (Next.js 13+), create a client component to handle tracking. Use trackAppRouter and pass both pathname and searchParams to track the full URL including query parameters:

"use client";

import { trackAppRouter } from "@socialgouv/matomo-next";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";

const MATOMO_URL = process.env.NEXT_PUBLIC_MATOMO_URL;
const MATOMO_SITE_ID = process.env.NEXT_PUBLIC_MATOMO_SITE_ID;

export function MatomoAnalytics() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    trackAppRouter({
      url: MATOMO_URL,
      siteId: MATOMO_SITE_ID,
      pathname,
      searchParams, // Pass URLSearchParams object directly
      // Optional: Enable additional features
      enableHeatmapSessionRecording: true,
      enableHeartBeatTimer: true,
    });
  }, [pathname, searchParams]);

  return null;
}

Notes:

Add this component to your root layout wrapped in a Suspense boundary (required for useSearchParams):

// app/layout.js
import { Suspense } from "react";
import { MatomoAnalytics } from "./matomo";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}

        <Suspense fallback={null}>
          <MatomoAnalytics />
        </Suspense>
      </body>
    </html>
  );
}

Note: The Suspense boundary is required when using useSearchParams() in the App Router.

App Router Features

The App Router implementation includes the following features:

  • Automatic route tracking: Detects changes in both pathname and search parameters
  • Search tracking: Automatically uses trackSiteSearch for /recherche and /search routes
  • Clean URLs: Tracks only the pathname without query strings (Matomo best practice)
  • Referrer tracking: Properly tracks the previous page as referrer
  • Custom callbacks: Supports onRouteChangeStart and onRouteChangeComplete hooks

Exclude tracking some routes

This wont track /login.php or any url containing ?token=.

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  excludeUrlsPatterns: [/^\/login.php/, /\?token=.+/],
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  excludeUrlsPatterns: [/^\/login.php/, /\?token=.+/],
});

Custom search tracking

Custom search keyword parameter

By default, the search tracking feature looks for a q parameter in the URL (e.g., /search?q=my+query). If your application uses a different parameter name for search queries, you can customize it:

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  searchKeyword: "query", // Will track searches from /search?query=my+search
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  searchKeyword: "query", // Will track searches from /search?query=my+search
});

Custom search routes

By default, search tracking is enabled for /recherche and /search routes. You can define custom routes that should be tracked as search pages:

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  searchRoutes: ["/find", "/discover", "/rechercher"], // Custom search routes
  searchKeyword: "q", // Optional: customize the search parameter
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  searchRoutes: ["/find", "/discover", "/rechercher"], // Custom search routes
  searchKeyword: "q", // Optional: customize the search parameter
});

When a user visits any of the defined search routes, the library will automatically use trackSiteSearch instead of trackPageView.

Clean URLs

By default, matomo-next tracks URLs with all their query parameters and hash fragments. You can enable URL cleaning to remove these parameters before tracking, which is useful for:

  • Removing sensitive data (tokens, user IDs, etc.) from tracked URLs
  • Normalizing URLs for better analytics aggregation
  • Improving privacy compliance

Important: Search routes (/recherche, /search, or custom searchRoutes) automatically keep their query parameters even when cleanUrl is enabled, to preserve search tracking functionality with trackSiteSearch.

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  cleanUrl: true, // Remove query params and hash fragments from tracked URLs
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  cleanUrl: true, // Remove query params and hash fragments from tracked URLs
});

Behavior examples:

// With cleanUrl: false (default)
// URL: /products?id=123&ref=home#section
// Tracked as: /products?id=123&ref=home#section

// With cleanUrl: true
// URL: /products?id=123&ref=home#section
// Tracked as: /products

// Search routes preserve query params even with cleanUrl: true
// URL: /search?q=keyword&category=docs&page=2
// Tracked as: /search?q=keyword&category=docs&page=2
// + trackSiteSearch("keyword")

Disable cookies

To disable cookies (for better GDPR compliance) set the disableCookies flag to true.

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  disableCookies: true,
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  disableCookies: true,
});

Track additional events

Type-safe event tracking (recommended)

Use the sendEvent helper for type-safe event tracking with auto-completion:

import { sendEvent } from "@socialgouv/matomo-next";

// Basic event with category and action
sendEvent({ category: "contact", action: "click phone" });

// Event with optional name parameter
sendEvent({
  category: "video",
  action: "play",
  name: "intro-video",
});

// Event with optional name and value parameters
sendEvent({
  category: "purchase",
  action: "buy",
  name: "product-123",
  value: "99.99",
});

Advanced tracking with push

For advanced use cases or custom tracking, use the push function directly:

import { push } from "@socialgouv/matomo-next";

// Track custom events
push(["trackEvent", "contact", "click phone"]);

// Track custom dimensions
push(["setCustomDimension", 1, "premium-user"]);

// Any other Matomo tracking method
push(["trackGoal", 1]);

Enable Heatmap & Session Recording

To enable Matomo's Heatmap & Session Recording feature:

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  enableHeatmapSessionRecording: true,
  heatmapConfig: {
    // Optional: capture keystrokes (default: false)
    captureKeystrokes: false,
    // Optional: capture only visible content (default: false, captures full page)
    captureVisibleContentOnly: false,
  },
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  enableHeatmapSessionRecording: true,
  heatmapConfig: {
    // Optional: capture keystrokes (default: false)
    captureKeystrokes: false,
    // Optional: capture only visible content (default: false, captures full page)
    captureVisibleContentOnly: false,
  },
});

The Heatmap & Session Recording plugin will be automatically loaded and configured. It will:

  • Load the HeatmapSessionRecording/tracker.min.js plugin
  • Configure keystroke capture and visible content settings
  • Enable the recording after page load

Enable HeartBeat Timer

To accurately measure time spent on pages, enable the HeartBeat Timer:

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  enableHeartBeatTimer: true,
  heartBeatTimerInterval: 15, // Optional: interval in seconds (default: 15)
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  enableHeartBeatTimer: true,
  heartBeatTimerInterval: 15, // Optional: interval in seconds (default: 15)
});

The HeartBeat Timer sends periodic requests to Matomo to measure how long visitors stay on pages. This is particularly useful for tracking engagement on single-page applications.

Debug Mode

Enable debug mode to see console logs for tracking events, excluded URLs, and Heatmap & Session Recording operations. This is useful for troubleshooting and development.

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  debug: true, // Enable debug logging
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  debug: true, // Enable debug logging
});

When debug is enabled, you will see console logs for:

  • Matomo initialization warnings
  • Excluded URL tracking (when URLs match excludeUrlsPatterns)
  • Heatmap & Session Recording plugin loading and configuration
  • Any errors during script loading

Note: Debug mode should be disabled in production to avoid cluttering the console.

Content-Security-Policy

If you use a Content-Security-Policy header with a nonce attribute, you can pass it to the initialization function to allow the script to be executed.

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  nonce: "123456789",
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  nonce: "123456789",
});

As the matomo-next injects a matomo script, if you use strict Trusted Types, you need to allow the script tag to be created by adding our policy name to your trusted types directive.

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types matomo-next;

You can set a custom policy name by passing it to the initialization function.

Pages Router:

trackPagesRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  trustedPolicyName: "your-custom-policy-name",
});

App Router:

trackAppRouter({
  url: MATOMO_URL,
  siteId: MATOMO_SITE_ID,
  pathname,
  searchParams,
  trustedPolicyName: "your-custom-policy-name",
});

Extensibility

The initialization functions have optional callback properties that allow for custom behavior to be added:

  • onRouteChangeStart(path: string) => void: This callback is triggered when the route is about to change. For Pages Router, it uses Next Router event routeChangeStart. For App Router, it's called when the pathname or searchParams change. It receives the new path as a parameter.

  • onRouteChangeComplete(path: string) => void: This callback is triggered when the route change is complete. For Pages Router, it uses Next Router event routeChangeComplete. For App Router, it's called after the page view is tracked. It receives the new path as a parameter.

  • onInitialization() => void: This callback is triggered when the function is first initialized. It does not receive any parameters. It could be useful to use it if you want to add parameter to Matomo when the page is render the first time.

  • onScriptLoadingError() => void: This callback is triggered when the script does not load. It does not receive any parameters. useful to detect ad-blockers.

Example with Pages Router:

import React, { useEffect } from "react";
import { trackPagesRouter } from "@socialgouv/matomo-next";

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    trackPagesRouter({
      url: process.env.NEXT_PUBLIC_MATOMO_URL,
      siteId: process.env.NEXT_PUBLIC_MATOMO_SITE_ID,
      onRouteChangeStart: (path) => {
        console.log("Route change started:", path);
        // Your custom logic here
      },
      onRouteChangeComplete: (path) => {
        console.log("Route change completed:", path);
        // Your custom logic here
      },
      onInitialization: () => {
        console.log("Matomo initialized");
      },
      onScriptLoadingError: () => {
        console.error("Failed to load Matomo script");
      },
    });
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;

Example with App Router:

"use client";

import { trackAppRouter } from "@socialgouv/matomo-next";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";

export function MatomoAnalytics() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    trackAppRouter({
      url: process.env.NEXT_PUBLIC_MATOMO_URL,
      siteId: process.env.NEXT_PUBLIC_MATOMO_SITE_ID,
      pathname,
      searchParams, // Pass directly without .toString()
      onRouteChangeStart: (path) => {
        console.log("Route change started:", path);
        // Your custom logic here
      },
      onRouteChangeComplete: (path) => {
        console.log("Route change completed:", path);
        // Your custom logic here
      },
      onInitialization: () => {
        console.log("Matomo initialized");
      },
      onScriptLoadingError: () => {
        console.error("Failed to load Matomo script");
      },
    });
  }, [pathname, searchParams]);

  return null;
}

About

Matomo for Next.js applications

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 17