fix: normalize page paths — strip UTM params and trailing slashes

This commit is contained in:
Usman Baig
2026-03-13 01:19:23 +01:00
parent 6edd5ac0b6
commit 1d71a13df4
2 changed files with 31 additions and 4 deletions

View File

@@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **Screen size fallback now works correctly.** A variable naming issue prevented the fallback screen dimensions from being read when the primary value wasn't available. Screen size data is now reliably captured on all browsers.
- **Browser back/forward no longer double-counts pageviews.** Pressing the back or forward button could occasionally register two pageviews instead of one. The tracking script now correctly deduplicates these navigations.
- **Preloaded pages no longer count as visits.** Modern browsers sometimes preload pages in the background before you actually visit them. These ghost visits no longer inflate your pageview counts — only pages the visitor actually sees are tracked.
- **Marketing parameters no longer fragment your pages.** Pages like `/about?utm_source=google` and `/about?utm_campaign=spring` now correctly show as just `/about` in your Top Pages. UTM tags, Facebook click IDs, Google click IDs, and other tracking parameters are stripped from the page path so all visits to the same page are grouped together.
- **Trailing slashes no longer split pages.** `/about/` and `/about` now count as the same page instead of appearing as separate entries in your analytics.
## [0.15.0-alpha] - 2026-03-13

View File

@@ -225,6 +225,31 @@
return cachedSessionId;
}
// * Normalize path: strip trailing slash and UTM/marketing query parameters
var UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'fbclid', 'gclid', 'gad_source', 'msclkid', 'twclid', 'dclid', 'mc_cid', 'mc_eid', 'ref'];
function cleanPath() {
var pathname = window.location.pathname;
// * Strip trailing slash (but keep root /)
if (pathname.length > 1 && pathname.charAt(pathname.length - 1) === '/') {
pathname = pathname.slice(0, -1);
}
// * Strip UTM/marketing params, keep other query params
var search = window.location.search;
if (search) {
try {
var params = new URLSearchParams(search);
for (var i = 0; i < UTM_PARAMS.length; i++) {
params.delete(UTM_PARAMS[i]);
}
var remaining = params.toString();
if (remaining) pathname += '?' + remaining;
} catch (e) {
// * URLSearchParams not supported — send path without query
}
}
return pathname;
}
// * Refresh dedup: skip pageview if the same path was tracked within 5 seconds
// * Prevents inflated pageview counts from F5/refresh while allowing genuine revisits
var REFRESH_DEDUP_WINDOW = 5000;
@@ -254,7 +279,7 @@
var routeChangeTime = performance.now();
var isSpaNav = !!currentEventId;
const path = window.location.pathname + window.location.search;
const path = cleanPath();
// * Skip if same path was just tracked (refresh dedup)
if (isDuplicatePageview(path)) {
@@ -381,7 +406,7 @@
}
return;
}
var path = window.location.pathname + window.location.search;
var path = cleanPath();
var rawRef = document.referrer || '';
var referrer = '';
if (rawRef) {
@@ -569,7 +594,7 @@
if (!selector) return;
var now = Date.now();
var currentPath = window.location.pathname + window.location.search;
var currentPath = cleanPath();
if (!rageClickHistory[selector]) {
rageClickHistory[selector] = { times: [], lastFired: 0 };
@@ -661,7 +686,7 @@
return;
}
var currentPath = window.location.pathname + window.location.search;
var currentPath = cleanPath();
var clickX = String(Math.round(e.clientX));
var clickY = String(Math.round(e.clientY));
var effectDetected = false;