diff --git a/src/main.tsx b/src/main.tsx index cfd549c..1608dc6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -172,8 +172,15 @@ function Root() { // no-ops without a configured token; sends only allowlisted custom events // through the same-origin /rly proxy. The initial sanitized $pageview // emits once the SDK is ready; client-side route changes emit from -// useRoute, deduped on pathname. -void initAnalytics().then(() => capturePageview()).catch(() => {}); +// useRoute, deduped on pathname. The boot-level popstate listener covers +// back/forward for anonymous sessions too (useRoute only mounts inside the +// authenticated App) — pathname dedupe collapses the overlap. +void initAnalytics() + .then(() => { + capturePageview(); + window.addEventListener('popstate', () => capturePageview()); + }) + .catch(() => {}); createRoot(document.getElementById("root")!).render( diff --git a/test/posthog-analytics.test.mjs b/test/posthog-analytics.test.mjs index 21916d6..78c0e48 100644 --- a/test/posthog-analytics.test.mjs +++ b/test/posthog-analytics.test.mjs @@ -509,7 +509,10 @@ test('facade dedupes pageviews and gates on the public host', () => { test('pageview wiring: boot emit once, route changes and popstate covered', () => { const main = src('src/main.tsx'); - assert.match(main, /initAnalytics\(\)\.then\(\(\) => capturePageview\(\)\)/); + assert.match(main, /initAnalytics\(\)[\s\S]*?capturePageview\(\)/); + // boot-level popstate covers back/forward even before the authenticated + // App mounts; useRoute's own listener dedupes against it on pathname. + assert.match(main, /addEventListener\('popstate',[\s\S]*?capturePageview\(\)/); const pages = src('src/pages.ts'); // navigate + popstate each emit — dedup in capturePageview collapses // same-path repeats (StrictMode, hash/range-only history entries).