From 857162385fde01506f9aab13089bbd4dd2408721 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Sun, 9 Nov 2025 15:02:31 +0000 Subject: [PATCH] feat: add Matomo analytics tracking Tracking features: - Matomo analytics integration (site ID: 4) - Initial pageview tracking on load - HTMX language change tracking as virtual pageviews - Link tracking enabled for outbound clicks Security updates: - Update CSP to allow matomo.drolo.club for script-src and connect-src - Matomo script loaded asynchronously Implementation: - Added Matomo tracking code before - Track HTMX navigation events (language changes) - Preserve privacy with self-hosted Matomo instance --- internal/middleware/security.go | 4 ++-- templates/index.html | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/internal/middleware/security.go b/internal/middleware/security.go index 3ca4e7b..39e20bb 100644 --- a/internal/middleware/security.go +++ b/internal/middleware/security.go @@ -30,11 +30,11 @@ func SecurityHeaders(next http.Handler) http.Handler { // Content Security Policy (comprehensive) csp := "default-src 'self'; " + - "script-src 'self' 'unsafe-inline' https://unpkg.com https://code.iconify.design; " + + "script-src 'self' 'unsafe-inline' https://unpkg.com https://code.iconify.design https://matomo.drolo.club; " + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " + "font-src 'self' https://fonts.gstatic.com; " + "img-src 'self' data: https:; " + - "connect-src 'self' https://api.iconify.design; " + + "connect-src 'self' https://api.iconify.design https://matomo.drolo.club; " + "frame-ancestors 'self'; " + "base-uri 'self'; " + "form-action 'self'" diff --git a/templates/index.html b/templates/index.html index 355eb14..8335309 100644 --- a/templates/index.html +++ b/templates/index.html @@ -619,6 +619,33 @@ console.log('HTMX request successful:', evt.detail.pathInfo.requestPath); } }); + + // Track HTMX navigation events with Matomo + document.body.addEventListener('htmx:afterSwap', function(evt) { + if (typeof _paq !== 'undefined' && evt.detail.target.id === 'cv-content') { + // Track language change as virtual pageview + const lang = new URLSearchParams(window.location.search).get('lang') || 'en'; + _paq.push(['setCustomUrl', window.location.href]); + _paq.push(['setDocumentTitle', document.title]); + _paq.push(['trackPageView']); + } + }); + + + +