A lightweight, TypeScript-ready Matomo analytics integration for Next.js applications with support for both Pages Router and App Router.
- ✅ Pages Router & App Router Support - Works with both Next.js routing systems
- ✅ Automatic Page Tracking - Tracks route changes and page views automatically
- ✅ Search Tracking - Built-in search query tracking
- ✅ GDPR Compliant - Cookie-less tracking option
- ✅ Custom Events - Type-safe event tracking API
- ✅ A/B Testing - Built-in support for Matomo's A/B Testing plugin with React hooks
- ✅ Server-Side Proxy - Bypass ad-blockers by routing tracking through your own domain
- ✅ Heatmap & Session Recording - Optional user behavior visualization
- ✅ TypeScript Support - Full type safety and auto-completion
npm install @socialgouv/matomo-nextAdd the trackPagesRouter() call in your _app.js:
import { useEffect } from "react";
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;Create a client component for tracking with trackAppRouter():
"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,
});
}, [pathname, searchParams]);
return null;
}Add it to your root layout wrapped in a Suspense boundary:
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>
);
}- Advanced Configuration - All configuration options, HeartBeat timer, callbacks, and extensibility
- Event Tracking - Track custom user interactions
- A/B Testing - Integrate Matomo's A/B Testing plugin with React hooks
- Server-Side Proxy - Bypass ad-blockers by proxying tracking requests
- Heatmap & Session Recording - User behavior tracking and visualization
- Security & Privacy - CSP configuration and GDPR compliance
Use the sendEvent() helper for type-safe event tracking:
import { sendEvent } from "@socialgouv/matomo-next";
// Track a button click
sendEvent({ category: "contact", action: "click phone" });
// Track with additional context
sendEvent({
category: "video",
action: "play",
name: "intro-video",
value: 120,
});Run experiments using Matomo's A/B Testing plugin. Pass your test definitions to the tracker and use the useABTestVariant() hook in your components:
// Pass abTests to the tracker
trackAppRouter({
url: MATOMO_URL,
siteId: MATOMO_SITE_ID,
pathname,
searchParams,
abTests: [
{
name: "homepage-hero",
percentage: 100,
variations: [{ name: "original" }, { name: "new-hero" }],
},
],
});// In your component
import { useABTestVariant } from "@socialgouv/matomo-next";
export function HeroSection() {
const variant = useABTestVariant("homepage-hero");
if (variant === "new-hero") {
return <NewHeroSection />;
}
return <OriginalHeroSection />;
}See the A/B Testing documentation for scheduling, custom triggers, and advanced usage.
Bypass ad-blockers by routing Matomo requests through your own domain using withMatomoProxy():
By default, the public proxy endpoint is generated under your own API namespace: /api/{random}.
// next.config.mjs
import { withMatomoProxy } from "@socialgouv/matomo-next";
export default withMatomoProxy({
matomoUrl: "https://analytics.example.com",
})(nextConfig);matomoUrl is required here because it’s the server-side target the proxy forwards to
(stored in MATOMO_PROXY_TARGET, not exposed to the browser).
// In your tracker component
import { trackAppRouter } from "@socialgouv/matomo-next";
// When the proxy is configured, calls are automatically routed through your own domain.
// This also hides the usual `matomo.js` / `matomo.php` filenames behind opaque build-time
// random names (some blockers match on those filenames).
trackAppRouter({ siteId: "1", pathname, searchParams });See the Server-Side Proxy documentation for custom paths, chaining with other plugins, and security considerations.
| Option | Type | Description | Default | Docs |
|---|---|---|---|---|
url |
string |
Matomo instance URL | - | Required unless using the server-side proxy |
siteId |
string |
Matomo site ID | - | Required |
useProxy |
boolean |
Prefer proxy (path + opaque filenames) when available | true |
Server-side proxy |
pathname |
string |
Current pathname (App Router only) | - | Required for App Router |
searchParams |
URLSearchParams |
URL search params (App Router only) | - | Required for App Router |
jsTrackerFile |
string |
Custom JS tracker filename | "matomo.js" (or proxy-provided opaque name) |
Advanced |
phpTrackerFile |
string |
Custom PHP tracker filename | "matomo.php" (or proxy-provided opaque name) |
Advanced |
excludeUrlsPatterns |
RegExp[] |
URLs to exclude from tracking | [] |
Advanced |
disableCookies |
boolean |
Cookie-less tracking | false |
Advanced |
cleanUrl |
boolean |
Remove query params from URLs | false |
Advanced |
searchKeyword |
string |
Search query parameter name | "q" |
Advanced |
searchRoutes |
string[] |
Custom search route paths | ["/recherche", "/search"] |
Advanced |
enableHeartBeatTimer |
boolean |
Track time on page | false |
Advanced |
heartBeatTimerInterval |
number |
HeartBeat timer interval (seconds) | 15 (Matomo default) |
Advanced |
enableHeatmapSessionRecording |
boolean |
Enable session recording | false |
Heatmap |
heatmapConfig |
HeatmapConfig |
Heatmap configuration object | {} |
Heatmap |
debug |
boolean |
Enable debug logs | false |
Advanced |
nonce |
string |
CSP nonce value | - | Security |
trustedPolicyName |
string |
Trusted Types policy name | "matomo-next" |
Security |
onInitialization |
() => void |
Callback on init | - | Advanced |
onRouteChangeStart |
(path) => void |
Callback on route change start | - | Advanced |
onRouteChangeComplete |
(path) => void |
Callback on route change complete | - | Advanced |
onScriptLoadingError |
() => void |
Callback on script loading error | - | Advanced |
abTests |
ABTestDefinition[] |
A/B test experiments to register | - | A/B Testing |
See complete configuration options for full details.
Contributions are welcome! Please feel free to submit a Pull Request.
