- Add
@openmost/nuxt-matomodependency to your project
# Using pnpm
pnpm add @openmost/nuxt-matomo
# Using yarn
yarn add @openmost/nuxt-matomo
# Using npm
npm install @openmost/nuxt-matomo- Add
@openmost/nuxt-matomoto themodulessection ofnuxt.config.ts
export default defineNuxtConfig({
modules: [
'@openmost/nuxt-matomo'
]
})- Configure the module. Either pass options in
nuxt.config.tsunder thematomokey:
export default defineNuxtConfig({
modules: ['@openmost/nuxt-matomo'],
matomo: {
host: 'https://matomo.example.com',
containerId: 'xxxxxxxx',
// Optional. Defaults to 'dataLayer' (GTM-style queue).
// Set to '_mtm' to push directly into Matomo's own queue.
dataLayerName: 'dataLayer',
},
})…or via runtimeConfig.public.matomo, which lets you override per environment:
export default defineNuxtConfig({
modules: ['@openmost/nuxt-matomo'],
runtimeConfig: {
public: {
matomo: {
host: 'https://matomo.example.com',
containerId: 'xxxxxxxx',
dataLayerName: 'dataLayer',
},
},
},
})If both are set, runtimeConfig (and the env vars below) win.
| Option | Module key | Runtime config key | Env var | Default |
|---|---|---|---|---|
| Matomo URL | host |
runtimeConfig.public.matomo.host |
NUXT_PUBLIC_MATOMO_HOST |
— |
| MTM container id | containerId |
runtimeConfig.public.matomo.containerId |
NUXT_PUBLIC_MATOMO_CONTAINER_ID |
— |
| dataLayer name | dataLayerName |
runtimeConfig.public.matomo.dataLayerName |
NUXT_PUBLIC_MATOMO_DATA_LAYER_NAME |
dataLayer |
- You can override values from a
.envfile:
NUXT_PUBLIC_MATOMO_HOST=https://matomo.example.com
NUXT_PUBLIC_MATOMO_CONTAINER_ID=xxxxxxxx
# Optional: write to Matomo's native queue instead of GTM's dataLayer
# NUXT_PUBLIC_MATOMO_DATA_LAYER_NAME=_mtm
- Configure your Matomo Tag Manager container to track SPA page views
This module fixes SPA tracking with Matomo Tag Manager by emitting a page_view custom event into the dataLayer on every route change, with the new page's URL and title already resolved. You then wire your Matomo Analytics tag to read those values from the dataLayer and trigger on that custom event.
In your Matomo Tag Manager container:
- Enable GTM
dataLayersynchronization on the container- Open your container's settings (Admin → Container → Edit).
- Turn on the "Synchronize with GTM Data Layer events" option (also labelled "Sync events from the GTM Data Layer" depending on your Matomo version).
- This is required because
useDataLayerPush(and the automaticpage_viewpush) writes to the standard GTMwindow.dataLayerarray. Without this option, Matomo Tag Manager listens to its own_mtmqueue only and will never see the events emitted by this module.
- Create a Data Layer Variable named
DLV - page_url- Type: Data-Layer Variable
- Data Layer Variable Name:
page_url
- Create a Data Layer Variable named
DLV - page_title- Type: Data-Layer Variable
- Data Layer Variable Name:
page_title
- Create a Custom Event trigger named
CE - page_view- Type: Custom Event
- Event Name:
page_view
- Edit your Matomo Analytics tag
- Set the Custom Page URL field to
{{DLV - page_url}} - Set the Custom Page Title field to
{{DLV - page_title}} - Set the trigger to
CE - page_view(replace any default Page view trigger so the tag fires once per SPA navigation, not twice).
- Set the Custom Page URL field to
- Publish a new version of your container.
On every route change, the module pushes the following object into the configured queue (window.dataLayer by default, or whatever you set dataLayerName to):
dataLayer.push({
event: 'page_view',
page_url: '/', // example value — current route fullPath
referrer_url: '/previous', // example value — previous route fullPath
page_title: 'My page title', // example value — document.title once resolved
})The push is deferred until after the new page has mounted and useSeoMeta / useHead has flushed the title to the document, so page_title always reflects the destination page.
dataLayer(default) — the GTM-style array. Use this with the "Synchronize with GTM Data Layer events" option enabled in your Matomo Tag Manager container, so MTM relays each push to its own queue. This is the recommended setup for sites that already have other GTM-style tooling._mtm— Matomo Tag Manager's native queue. Use this when you don't want to enable the GTM sync option (or you don't have adataLayerat all). MTM picks events up directly without any extra container setting.
Switch via dataLayerName: '_mtm' in nuxt.config.ts or NUXT_PUBLIC_MATOMO_DATA_LAYER_NAME=_mtm in your .env.
This module allow you to use some helpful composables, here is the list :
The useDataLayerPush() composable pushes any payload to the configured queue (defaults to window.dataLayer, or whatever you set dataLayerName to — e.g. _mtm).
<template>
<button @click="onClick">Click me</button>
</template>
<script setup>
function onClick(){
useDataLayerPush({
event: 'some_event',
foo: 'bar',
})
}
</script>The useMatomoEvent() composable allow you to send events to Matomo directly from your components.
It also handle custom dimension as 5th parameter (optional).
<template>
<button @click="onClick">Click me</button>
</template>
<script setup>
function onClick(){
useMatomoEvent('Category', 'Action', 'event_name', 23, {dimension1: 'Some value'})
}
</script>The useMatomoGoal() composable allow you to convert goals from your components.
<template>
<button @click="onClick">Click me</button>
</template>
<script setup>
function onClick(){
useMatomoGoal(4) // 4 is the goal ID
}
</script>The useMatomoCustomDimension() composable allow you to send data to your custom dimensions.
<template>
<button @click="onClick">Click me</button>
</template>
<script setup>
function onClick() {
useMatomoCustomDimension(1, 'My custom value') // 1 is the custom dimension ID
}
</script>The useMatomoCustomVariable() composable allow you to send data to your custom variables.
<template>
<button @click="onClick">Click me</button>
</template>
<script setup>
function onClick() {
useMatomoCustomVariable(1, 'variable name', 'variable value', 'page')
}
</script>The useMatomoSearch() composable wraps Matomo's trackSiteSearch API to log internal-search queries from your components.
<template>
<form @submit.prevent="onSearch">
<input v-model="query" type="search">
<button type="submit">Search</button>
</form>
</template>
<script setup>
const query = ref('')
const results = ref([])
async function onSearch() {
results.value = await $fetch('/api/search', { params: { q: query.value } })
// keyword, category (or false), result count
useMatomoSearch(query.value, 'docs', results.value.length)
}
</script>Enjoy !