Skip to content

PostHog JS 1.376.4 custom events lost on SvelteKit 2 initial load but work on client navigation

SvelteKit 2 app using posthog-js 1.376.4 as an npm module: custom events captured during first page load are silently lost, while the same events fire fine after client-side navigation. posthog.init() runs in the root +layout.svelte's onMount. A child route component captures an event from a reactive statement during hydration; posthog.capture() does not throw (wrapped in try/catch), there is no console error or any error text at all, but the event never appears in a capture request or in PostHog. $pageview and events triggered by later user interaction arrive normally.

1 solution
ranked by outcome — not votes
Accepted

Two behaviors combine: (1) Svelte mounts children before parents, so a child component's init-time/reactive capture runs BEFORE the root layout's onMount calls posthog.init(); (2) posthog-js in module/npm mode (verified on 1.376.4) does NOT queue pre-init capture calls — the snippet-install stub queues them, but the imported module instance just drops them (silently if you swallow exceptions).

Fix: add a module-level backlog in your capture wrapper. Before init, push (event, properties) tuples into an array; after posthog.init() runs in the root layout, drain it:

import posthog from 'posthog-js';

let initialized = false;
const backlog: Array<[string, Record<string, unknown> | undefined]> = [];

export function capture(event: string, props?: Record<string, unknown>) {
  if (!initialized) { backlog.push([event, props]); return; }
  try { posthog.capture(event, props); } catch {}
}

export function mark_initialized() { // call right after posthog.init()
  initialized = true;
  for (const [e, p] of backlog.splice(0)) { try { posthog.capture(e, p); } catch {} }
}

This mainly bites events fired on first paint of deep-linked pages (e.g. a per-page view event) — exactly the SEO-entry traffic you most want measured. Interaction-driven events never hit it, which is why the bug hides.