A minimal yet powerful React package that integrates Matomo analytics with React applications, supporting React Router, Next.js (Pages & App Router), and any SSR framework.
Written in TypeScript but designed to be fully compatible with JavaScript projects as well.
- β SSR Compatible - Works with Next.js (Pages & App Router), Remix, and other SSR frameworks
- β Automatic Page View Tracking for React Router and Next.js
- β
Custom Event Tracking via
useMatomo()hook - β
Matomo Initialization via
MatomoProvider - β Complete Consent Management - Full GDPR/Cookie consent support with opt-in/opt-out
- β Cookie Control: Granular control over tracking cookies
- β TypeScript-first, JavaScript-friendly
- β Tree-shakeable ESM/CJS output
- β Modular Exports - Import only what you need (React Router / Next.js)
- β Zero configuration for basic use cases
npm install matomo-tracker-reactThe MatomoProvider is now SSR-compatible and works with React Router, Next.js, and other React frameworks.
Important: Use subpath imports to avoid dependency conflicts. Next.js projects should import from
matomo-tracker-react/nextjs, and React Router projects frommatomo-tracker-react/react-router.
For automatic page view tracking with React Router, use the MatomoRouterTracker component:
import {
MatomoProvider,
MatomoRouterTracker,
} from "matomo-tracker-react/react-router";
import { BrowserRouter } from "react-router-dom";
<MatomoProvider
urlBase="https://matomo.example.com"
siteId="1" // Matomo Site ID
trackCookies={false} // optional, default: true
disabled={false} // optional, default: false. If true, disables all tracking.
>
<BrowserRouter>
<MatomoRouterTracker />
<App />
</BrowserRouter>
</MatomoProvider>;For Next.js with Pages Router, use the useMatomoNextRouter hook in your _app.tsx:
// pages/_app.tsx
import {
MatomoProvider,
useMatomoNextRouter,
} from "matomo-tracker-react/nextjs";
import type { AppProps } from "next/app";
function MatomoTracker() {
useMatomoNextRouter(); // Automatically tracks page views on route changes
return null;
}
function MyApp({ Component, pageProps }: AppProps) {
return (
<MatomoProvider urlBase="https://matomo.example.com" siteId="1">
<MatomoTracker />
<Component {...pageProps} />
</MatomoProvider>
);
}
export default MyApp;For Next.js with App Router, create a client component wrapper:
// app/providers.tsx
"use client";
import {
MatomoProvider,
MatomoNextRouterTracker,
} from "matomo-tracker-react/nextjs";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<MatomoProvider urlBase="https://matomo.example.com" siteId="1">
<MatomoNextRouterTracker />
{children}
</MatomoProvider>
);
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}If you don't need automatic route tracking, you can use MatomoProvider alone and track page views manually:
import { MatomoProvider } from "matomo-tracker-react";
<MatomoProvider urlBase="https://matomo.example.com" siteId="1">
<App />
</MatomoProvider>;Then use the useMatomo hook to track page views manually:
import { useMatomo } from "matomo-tracker-react";
import { useEffect } from "react";
function MyPage() {
const { trackPageView } = useMatomo();
useEffect(() => {
trackPageView("Custom Page Title");
}, []);
return <div>My Page</div>;
}import { useMatomo } from "matomo-tracker-react";
const MyComponent = () => {
const { trackEvent } = useMatomo();
const handleClick = () => {
trackEvent("Button", "Click", "My CTA Button");
};
return <button onClick={handleClick}>Click Me</button>;
};| Prop | Type | Required | Description |
|---|---|---|---|
children |
ReactNode |
β | Your application components. |
urlBase |
string |
β | Base URL of your Matomo instance (e.g., https://your-matomo-domain.com). |
siteId |
string or number |
β | Your Matomo website ID. |
trackCookies? |
boolean |
β | If false, disables cookies (disableCookies: true). Default: true. |
disabled? |
boolean |
β | If true, disables all tracking. Default: false. |
Returns an object with:
trackEvent(category: string, action: string, name?: string, value?: number): Tracks a custom event.trackPageView(customTitle?: string): Tracks a page view. Useful for SPAs if automatic tracking needs fine-tuning or if you want to set a custom title.trackGoal(goalId: number | string, revenue?: number): Tracks a conversion for a specific goal.setUserId(userId: string): Sets or updates a User ID for the current visitor.trackLink(url: string, linkType: 'link' | 'download'): Tracks an outbound link click or a download.pushInstruction(instruction: any[]): Allows pushing any raw instruction to the Matomo_paqarray for advanced use cases.
requireConsent(): Requires user consent before any tracking begins (opt-in mode).setConsentGiven(): Marks that user has given consent, tracking will begin.requireCookieConsent(): Requires consent for cookies only (tracking without cookies until consent).setCookieConsentGiven(): Enables tracking cookies after consent is given.forgetCookieConsentGiven(): Revokes cookie consent and removes tracking cookies.optUserOut(): Completely opts the user out of tracking.forgetUserOptOut(): Opts the user back into tracking.deleteCookies(): Deletes all Matomo cookies for the current domain.
This package provides complete consent management support for GDPR compliance and Cookie Consent Platforms (CMP).
Use this when you want to track users without cookies until they give consent:
// app/providers.tsx (Next.js App Router)
"use client";
import {
MatomoProvider,
MatomoNextRouterTracker,
} from "matomo-tracker-react/nextjs";
import { useEffect, useState } from "react";
import { useMatomo } from "matomo-tracker-react/nextjs";
function ConsentManager({ consent }: { consent: boolean | null }) {
const {
requireCookieConsent,
setCookieConsentGiven,
forgetCookieConsentGiven,
deleteCookies,
} = useMatomo();
useEffect(() => {
// Initialize cookie consent mode
requireCookieConsent();
}, [requireCookieConsent]);
useEffect(() => {
if (consent === true) {
// User accepts: enable cookies
setCookieConsentGiven();
} else if (consent === false) {
// User rejects: remove cookies
forgetCookieConsentGiven();
deleteCookies();
}
}, [consent, setCookieConsentGiven, forgetCookieConsentGiven, deleteCookies]);
return null;
}
export function Providers({ children }: { children: React.ReactNode }) {
const [consent, setConsent] = useState<boolean | null>(null);
useEffect(() => {
// Load consent from your CMP (Cookie Consent Platform)
const savedConsent = localStorage.getItem("cookie-consent");
if (savedConsent !== null) {
setConsent(savedConsent === "true");
}
}, []);
return (
<MatomoProvider
urlBase={process.env.NEXT_PUBLIC_MATOMO_URL!}
siteId={process.env.NEXT_PUBLIC_MATOMO_SITE_ID!}
>
<ConsentManager consent={consent} />
<MatomoNextRouterTracker />
{children}
</MatomoProvider>
);
}Use this when you want to block ALL tracking until user gives explicit consent:
"use client";
import {
MatomoProvider,
MatomoNextRouterTracker,
} from "matomo-tracker-react/nextjs";
import { useEffect, useState } from "react";
import { useMatomo } from "matomo-tracker-react/nextjs";
function ConsentManager({ consent }: { consent: boolean | null }) {
const { requireConsent, setConsentGiven, optUserOut, deleteCookies } =
useMatomo();
useEffect(() => {
// Require consent before ANY tracking
requireConsent();
}, [requireConsent]);
useEffect(() => {
if (consent === true) {
// User accepts: start tracking
setConsentGiven();
} else if (consent === false) {
// User rejects: disable completely
optUserOut();
deleteCookies();
}
}, [consent, setConsentGiven, optUserOut, deleteCookies]);
return null;
}
export function Providers({ children }: { children: React.ReactNode }) {
const [consent, setConsent] = useState<boolean | null>(null);
return (
<MatomoProvider
urlBase={process.env.NEXT_PUBLIC_MATOMO_URL!}
siteId={process.env.NEXT_PUBLIC_MATOMO_SITE_ID!}
>
<ConsentManager consent={consent} />
{consent && <MatomoNextRouterTracker />}
{children}
</MatomoProvider>
);
}"use client";
import { useMatomo } from "matomo-tracker-react/nextjs";
import { useEffect } from "react";
function CMPIntegration() {
const {
requireCookieConsent,
setCookieConsentGiven,
forgetCookieConsentGiven,
deleteCookies,
} = useMatomo();
useEffect(() => {
requireCookieConsent();
// Listen to your CMP events
window.addEventListener("cookieConsentAccepted", () => {
setCookieConsentGiven();
});
window.addEventListener("cookieConsentRejected", () => {
forgetCookieConsentGiven();
deleteCookies();
});
}, [
requireCookieConsent,
setCookieConsentGiven,
forgetCookieConsentGiven,
deleteCookies,
]);
return null;
}| Method | Description | Use Case |
|---|---|---|
requireConsent() |
Blocks ALL tracking until consent | Full opt-in (strictest) |
setConsentGiven() |
Enables tracking after opt-in | After user accepts |
requireCookieConsent() |
Tracks without cookies until consent | Cookie-only opt-in |
setCookieConsentGiven() |
Enables tracking cookies | After cookie consent |
forgetCookieConsentGiven() |
Disables cookies, keeps tracking | User rejects cookies |
optUserOut() |
Completely stops tracking | User opts out |
forgetUserOptOut() |
Re-enables tracking | User opts back in |
deleteCookies() |
Removes all Matomo cookies | Cleanup on consent withdrawal |
This package can use Vite or tsc to build and bundle the code.
npm run build
npm publish --access publicThe npm run build script generates two versions of the library:
lib/: CommonJS (CJS) modules, for broad compatibility (referenced by themainfield inpackage.json).es/: ES Modules (ESM), for modern bundlers that support tree-shaking (referenced by themodulefield inpackage.json).
If you see an error in your browser console like "Laden fehlgeschlagen fΓΌr das <script> mit der Quelle..." or "Failed to load resource..." for matomo.js, even if you can access the matomo.js URL directly in your browser, consider these common causes:
-
CORS (Cross-Origin Resource Sharing):
- Problem: Your React app (e.g.,
http://localhost:3000) and your Matomo instance (e.g.,https://matomo.example.com) are on different origins. - Solution: Configure your Matomo server to send the
Access-Control-Allow-Originheader, allowing requests from your React app's domain. For example,Access-Control-Allow-Origin: http://localhost:3000.
- Problem: Your React app (e.g.,
-
Mixed Content:
- Problem: Your React app is on
httpsbutmatomo.jsis requested viahttp. - Solution: Ensure both your app and Matomo (and the
urlBase/srcUrlprovided) usehttps.
- Problem: Your React app is on
-
Content Security Policy (CSP):
- Problem: Your app's CSP might be blocking scripts from the Matomo domain.
- Solution: Update your CSP to include your Matomo domain in
script-src(e.g.,script-src 'self' https://matomo.example.com;).
-
Ad Blockers/Browser Extensions:
- Problem: Extensions might block the script when loaded by your app.
- Solution: Temporarily disable extensions to test. If an extension is the cause, consider whitelisting.
This library is fully compatible with Server-Side Rendering (SSR) and works seamlessly with Next.js, Remix, and other React frameworks.
- β No client-only code execution during SSR - All tracking logic only runs in the browser
- β
typeof window !== 'undefined'checks throughout the codebase - β
Next.js Pages Router support via
useMatomoNextRouterhook - β
Next.js App Router support via
MatomoNextRouterTrackercomponent - β
React Router support via
MatomoRouterTrackercomponent - β Manual tracking for custom frameworks
- MatomoProvider now works without requiring react-router-dom
- Automatic page tracking is optional and framework-specific
- No hydration errors - Provider safely initializes only on the client
- TypeScript support with full type safety
// pages/_app.tsx
import type { AppProps } from "next/app";
import { MatomoProvider, useMatomoNextRouter } from "matomo-tracker-for-react";
function MatomoTracker() {
useMatomoNextRouter();
return null;
}
export default function App({ Component, pageProps }: AppProps) {
return (
<MatomoProvider
urlBase={process.env.NEXT_PUBLIC_MATOMO_URL!}
siteId={process.env.NEXT_PUBLIC_MATOMO_SITE_ID!}
>
<MatomoTracker />
<Component {...pageProps} />
</MatomoProvider>
);
}import {
MatomoProvider,
MatomoRouterTracker,
} from "matomo-tracker-react/react-router";
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<MatomoProvider urlBase="https://matomo.example.com" siteId="1">
<BrowserRouter>
<MatomoRouterTracker />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
</MatomoProvider>
);
}// pages/_app.tsx
import type { AppProps } from "next/app";
import { MatomoProvider, useMatomoNextRouter } from "matomo-tracker-for-react";
function MatomoTracker() {
useMatomoNextRouter();
return null;
}
export default function App({ Component, pageProps }: AppProps) {
return (
<MatomoProvider
urlBase={process.env.NEXT_PUBLIC_MATOMO_URL!}
siteId={process.env.NEXT_PUBLIC_MATOMO_SITE_ID!}
trackCookies={true}
>
<MatomoTracker />
<Component {...pageProps} />
</MatomoProvider>
);
}// pages/index.tsx - Using the hook
import { useMatomo } from "matomo-tracker-react/nextjs";
export default function Home() {
const { trackEvent } = useMatomo();
return (
<button onClick={() => trackEvent("Button", "Click", "Homepage CTA")}>
Click Me
</button>
);
}// app/providers.tsx
"use client";
import {
MatomoProvider,
MatomoNextRouterTracker,
} from "matomo-tracker-react/nextjs";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<MatomoProvider
urlBase={process.env.NEXT_PUBLIC_MATOMO_URL!}
siteId={process.env.NEXT_PUBLIC_MATOMO_SITE_ID!}
>
<MatomoNextRouterTracker />
{children}
</MatomoProvider>
);
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}// app/components/TrackingButton.tsx - Using in a client component
"use client";
import { useMatomo } from "matomo-tracker-react/nextjs";
export function TrackingButton() {
const { trackEvent } = useMatomo();
return (
<button onClick={() => trackEvent("App", "Click", "Button")}>
Track Event
</button>
);
}"use client"; // For Next.js App Router
import { useMatomo } from "matomo-tracker-react/nextjs";
export function CheckoutButton() {
const { trackGoal } = useMatomo();
const handleCheckout = () => {
trackGoal(1, 99.99); // Goal ID 1, revenue 99.99
// ... proceed with checkout
};
return <button onClick={handleCheckout}>Complete Purchase</button>;
}import { useMatomo } from "matomo-tracker-react";
import { useEffect } from "react";
export function UserTracking({ userId }: { userId: string }) {
const { setUserId } = useMatomo();
useEffect(() => {
if (userId) {
setUserId(userId);
}
}, [userId, setUserId]);
return null;
}// For Next.js
"use client";
import {
MatomoProvider,
MatomoNextRouterTracker,
} from "matomo-tracker-react/nextjs";
export function Providers({ children }: { children: React.ReactNode }) {
const isDevelopment = process.env.NODE_ENV === "development";
return (
<MatomoProvider
urlBase={process.env.NEXT_PUBLIC_MATOMO_URL!}
siteId={process.env.NEXT_PUBLIC_MATOMO_SITE_ID!}
disabled={isDevelopment}
>
{!isDevelopment && <MatomoNextRouterTracker />}
{children}
</MatomoProvider>
);
}For advanced Matomo features not directly exposed by the API:
const { pushInstruction } = useMatomo();
// Set custom dimensions
pushInstruction(["setCustomDimension", 1, "Premium User"]);
// Set custom variables
pushInstruction(["setCustomVariable", 1, "UserType", "Premium", "visit"]);
// Enable heartbeat timer
pushInstruction(["enableHeartBeatTimer", 15]);
// Any Matomo _paq command
pushInstruction(["trackContentImpression", "Content Name", "Content Piece"]);For Next.js projects, add these to your .env.local:
NEXT_PUBLIC_MATOMO_URL=https://your-matomo-instance.com
NEXT_PUBLIC_MATOMO_SITE_ID=1Problem: Your component is trying to use the useMatomo hook outside of the MatomoProvider.
Solution: Make sure your component is wrapped in the provider:
// β
Correct
<MatomoProvider urlBase="..." siteId="...">
<YourComponent />
</MatomoProvider>
// β Incorrect
<YourComponent />Problem: Page views aren't being tracked automatically.
Solution: Make sure you're using the correct tracking component:
- React Router: Add
<MatomoRouterTracker /> - Next.js Pages Router: Use
useMatomoNextRouter()hook - Next.js App Router: Add
<MatomoNextRouterTracker />component
Problem: React hydration mismatch errors.
Solution: Make sure to use the 'use client' directive in components that use Matomo hooks:
"use client";
import { useMatomo } from "matomo-tracker-react";
// ... rest of your componentPossible causes:
- CORS: Configure your Matomo server to allow requests from your domain
- Mixed Content: Ensure both your app and Matomo use
https - CSP: Update Content Security Policy to allow scripts from Matomo domain
- Ad Blockers: May block Matomo scripts in development
This package is designed with privacy and GDPR compliance in mind:
- β Full Consent Management: Complete opt-in/opt-out support via dedicated methods
- β Cookie Control: Granular control over tracking cookies
- β Tracking Disabling: Can completely disable tracking when needed
- β
No Tracking Until Consent: Supports
requireConsent()for opt-in mode - β
Cookie-less Tracking: Track users without cookies using
requireCookieConsent() - β
Easy Cookie Deletion:
deleteCookies()method for consent withdrawal - β CMP Integration: Works with OneTrust, Cookiebot, and custom consent platforms
- Use
requireConsent()orrequireCookieConsent()before tracking - Implement a consent banner and connect it to Matomo consent methods
- Call
setCookieConsentGiven()orsetConsentGiven()only after user accepts - Provide a way for users to withdraw consent (call
deleteCookies()andoptUserOut()) - Document your data processing in your privacy policy
Note: This package provides the technical implementation. Legal compliance (GDPR, CCPA, etc.) depends on proper configuration in both your application and your Matomo instance.
- Add goal tracking (
trackGoal) - Add user ID support (
setUserId) - Add link/interaction tracking (
trackLink) - Basic React Router integration for page views
- SSR/Next.js support
- Add more helper hooks
- Add tests with Vitest or Jest
This project is a fork of matomo-tracker-for-react by JonasKenke.
Inspired by:
- Matomo docs on React integration
@datapunt/matomo-tracker-react(now deprecated)
MPL-2.0