diff --git a/CHANGELOG.md b/CHANGELOG.md index c4e0994..2677342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to Pulse (frontend and product) are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and Pulse uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html) with a **0.x.y** version scheme while in initial development. The leading `0` indicates that the public API and behaviour may change until we release **1.0.0**. +## [Unreleased] + +### Fixed + +- **More accurate pageview counts.** Refreshing a page no longer inflates your pageview numbers. The tracking script now detects when the same page is loaded again within a few seconds and skips the duplicate, so metrics like total pageviews, pages per session, and visit duration reflect real navigation instead of reload habits. + ## [0.15.0-alpha] - 2026-03-13 ### Added diff --git a/public/script.js b/public/script.js index 4ead55b..7e0ca6f 100644 --- a/public/script.js +++ b/public/script.js @@ -225,11 +225,42 @@ return cachedSessionId; } + // * 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; + var DEDUP_STORAGE_KEY = 'ciphera_last_pv'; + + function isDuplicatePageview(path) { + try { + var raw = sessionStorage.getItem(DEDUP_STORAGE_KEY); + if (raw) { + var last = JSON.parse(raw); + if (last.p === path && Date.now() - last.t < REFRESH_DEDUP_WINDOW) { + return true; + } + } + } catch (e) {} + return false; + } + + function recordPageview(path) { + try { + sessionStorage.setItem(DEDUP_STORAGE_KEY, JSON.stringify({ p: path, t: Date.now() })); + } catch (e) {} + } + // * Track pageview function trackPageview() { var routeChangeTime = performance.now(); var isSpaNav = !!currentEventId; + const path = window.location.pathname + window.location.search; + + // * Skip if same path was just tracked (refresh dedup) + if (isDuplicatePageview(path)) { + return; + } + if (currentEventId) { // * SPA nav: visibilitychange may not fire, so send previous page's metrics now sendMetrics(); @@ -239,8 +270,6 @@ lcpObserved = false; clsObserved = false; currentEventId = null; - - const path = window.location.pathname + window.location.search; const referrer = document.referrer || ''; const screen = { width: window.innerWidth || screen.width, @@ -265,6 +294,7 @@ keepalive: true, }).then(res => res.json()) .then(data => { + recordPageview(path); if (data && data.id) { currentEventId = data.id; // * For SPA navigations the browser never emits a new largest-contentful-paint