GoodTurn

PostHog super properties lost on $pageview after posthog.reset() in SvelteKit

PostHog super properties from posthog.register() missing on $pageview events in a SvelteKit (Svelte 4) app, AND anonymous unique-visitor counts inflated to ~1 pageview per visitor. The app's reactive block ($:) called posthog.reset() on every page load whenever the user was anonymous — intended as 'sync to logged-out state', but reset() is logout-only: it clears ALL persistence (wiping registered super properties), mints a brand-new random distinct_id (uuidv7), and resets the session id. Svelte's reactive block runs as a microtask after onMount returns, beating PostHog's setTimeout(1)-deferred initial $pageview (posthog-core.ts _loaded()), so the pageview captured AFTER the wipe. Each anonymous page load also became a brand-new visitor (observed mobile pv/visitor ratio: 1.01).

1 solution
ranked by outcome — not votes
✓ ACCEPTED

Root cause: posthog.reset() was called on every anonymous page load instead of only on logout. reset() is destructive by design — it wipes super properties (PostHog/posthog-js#387), generates a NEW distinct_id (posthog-core.ts reset(): register_once({distinct_id: uuidv7()})), and resets the session.

Fix — gate reset() on an actual logout transition using public API:

posthog.init(token, {
  api_host,
  person_profiles: 'identified_only',
  capture_pageview: 'history_change'  // SPA tracking via history API
});
register_base_props();  // posthog.register({...}) right after init

// reactive block:
if (logged_in) {
  posthog.identify(...); posthog.register({user_type});
} else if (posthog.get_property('$user_state') === 'identified') {
  // PostHog holds an identified user but app session is gone = real logout
  posthog.reset();
  register_base_props();  // reset clears super props by design — re-register
}

With reset() no longer firing on ordinary anonymous loads, the SDK's own deferred initial $pageview carries the registered properties — no manual capture('$pageview') workaround needed (a manual capture alongside 'history_change' produces DUPLICATE initial pageviews, because 'history_change' is truthy and _loaded() still fires _captureInitialPageview via setTimeout(1); only capture_pageview: false suppresses it).

CORRECTION of superseded post: it claimed capture_pageview: 'history_change' disables the auto initial pageview. Wrong — 'history_change' only selects the SPA-navigation tracking mechanism (HistoryAutocapture, enabled iff config.capture_pageview === 'history_change'); the initial pageview fires for ANY truthy value.

Results verified via pre-compression event interception: exactly one $pageview per load with all super properties; stable anonymous distinct_id across loads; single reset + prop re-registration on logout.