A SvelteKit app synced login state to PostHog with a reactive block that called posthog.reset() whenever the user was anonymous — i.e. on EVERY page load for EVERY logged-out visitor. Two silent data-quality failures followed:
Anonymous visitor inflation.
reset()mints a brand-new randomdistinct_id(register_once({distinct_id: uuidv7()})in posthog-core.tsreset()), so each anonymous page load counted as a new unique visitor. Observed signature: mobile (mostly anonymous) pv/visitor ratio of 1.01 vs desktop (mostly identified) 1.41. If your anonymous pv/visitor ≈ 1.0, check for this.Super properties missing from $pageview. Svelte's reactive
$:blocks run as microtasks afteronMountreturns; PostHog defers its initial$pageviewviasetTimeout(1)(macrotask) inside_loaded(). The microtask reset wiped theregister()ed properties before the deferred pageview captured them — only ~4% of pageviews carried the custom props.
The fix — reset only on a real logout transition, using public API:
} else if (posthog.get_property('$user_state') === 'identified') {
// PostHog still holds an identified user but the app session is gone
posthog.reset();
register_base_props(); // reset clears super props by design (posthog-js#387)
}$user_state ('anonymous' | 'identified') is a stable persistence key readable via the public get_property(); it survives reloads, so logout-via-full-page-reload is still detected. Re-register base super properties immediately after reset() — PostHog's documented position (PostHog/posthog-js#387) is that reset clears them by design.
Config corrections (both verified against posthog-js source, v1.376.4 and HEAD):
capture_pageview: 'history_change'does NOT disable the initial auto-$pageview — any truthy value fires it viasetTimeout(1). It only enablesHistoryAutocapture(SPA navigation tracking via pushState/replaceState/popstate patching), which fires solely on real pathname changes. Onlycapture_pageview: falsesuppresses the initial pageview.- Therefore: with reset() correctly gated, DON'T pair 'history_change' with a manual
posthog.capture('$pageview')— you get duplicate initial pageviews. The SDK's own deferred pageview picks up everythingregister()ed synchronously afterinit(), and even everything from the reactive-microtaskidentify()/register()calls, since they all run before the macrotask. - PostHog's official SvelteKit pattern (posthog.com/docs/libraries/svelte): init in
+layout.jsload with a moderndefaultsdate (setscapture_pageview: 'history_change'automatically); no afterNavigate wiring needed.