What doesn't work — lazy-initializing an error-tracking SDK to save boot time
Deferring Sentry.init (or any error-tracking SDK init) past the first paint to save boot time creates a structural blind window: every error thrown before the global handler is hooked is gone forever, and no retry or backfill can recover it.
The signal that gives it away
The dashboard shows "Waiting for events" (or stays completely empty) even though every other check passes: the SDK bundle is confirmed shipped (visible in the network tab / bundle analysis), the DSN/API key is correct and not rotated, and there's no CSP or network block on the ingest endpoint. This combination — SDK present, config correct, connectivity open, *still* zero events — is the tell. It means the SDK exists on the page but its global handler (window.onerror, window.onunhandledrejection, or the server-side equivalent) was not registered in time to see anything happen. A team can burn hours re-checking DSN, network, and CSP because those are the "obvious" suspects; the actual defect is a timing gap, not a config gap.
Concretely, in one observed case, the SDK was initialized via dynamic import() scheduled inside a "run when the browser is idle" callback (requestIdleCallback-style API), specifically to avoid delaying first paint. That is exactly the pattern the public Sentry community documents as broken for this purpose — deferring Sentry.init() to an idle callback or to "after the app has loaded its main code" means any exception thrown during that idle window is invisible to the SDK, because the SDK's own global handler isn't attached yet ([forum.sentry.io/t/errors-logged-before-sentry-init-in-the-browser](https://forum.sentry.io/t/errors-logged-before-sentry-init-in-the-browser/5952)).
How to fix it (operational detail)
The correct pattern is eager, synchronous init at boot — the SDK's global error handler must be the first thing attached, before any application code that could throw runs.
- Client side: call Sentry.init(...) synchronously at the top of the client entry point (e.g. a root hook/bootstrap file), not behind a dynamic import, not behind an idle-callback, not gated on a feature flag that resolves asynchronously.
- Server side (SSR frameworks): use whatever "instrumentation" hook the framework exposes to guarantee init runs *before* any request handler. In SvelteKit specifically, this is src/instrumentation.server.ts, gated behind kit.experimental.instrumentation.server in svelte.config.js (SvelteKit ≥2.31). The framework docs are explicit that this file "is guaranteed to be run prior to your application code being imported" precisely so that instrumentation/tracing/error-handling is attached before anything that could throw ([svelte.dev/docs/kit/observability](https://svelte.dev/docs/kit/observability)). Without it, even a Sentry.init placed early in a request handler still misses errors thrown during the framework's own request setup, because that setup runs first.
- Verify it actually worked, don't just verify it's configured. Checking that the init call exists in the source is not the same as checking that the handler is live at the moment it matters. The verification that catches this: open the page, and in the browser console check that the hook is actually installed (window.onerror is a function, not undefined), that the SDK global object exists and reports the expected version, then *force* an early error (e.g. setTimeout(() => { throw new ReferenceError('...') }, 0) right after load) and confirm a POST envelope reaches the ingest endpoint with a 200 status. Config review alone would have said "looks fine" while the real behavior was silently dropping errors — the same gap documented in Static verification doesn't prove runtime behavior — the SEED / ACTIVATE / VERIFY / NEGATIVE checklist.
- Sample-rate trade-off is a separate lever, don't confuse the two. You can keep tracesSampleRate and session-replay sampling low (or zero) to control cost/overhead without touching error capture at all — set sampleRate: 1.0 (capture all errors) independently of performance/replay sampling. Lazy-loading to save cost is solving a real problem (bundle weight, idle CPU) with the wrong lever; the sampling knobs solve the cost problem without creating a blind window.
What doesn't work
- `requestIdleCallback` (or equivalent "defer until idle") gating the SDK init. This is the exact anti-pattern: it optimizes for perceived load performance at the cost of a genuinely unrecoverable data gap. There is no way to backfill errors that happened before the handler existed — unlike a metrics gap, this isn't "missing data you can estimate," it's "events that were never observed."
- Dynamic `import()` of the SDK triggered by an error handler that's itself set up lazily. Some documented workarounds set up a *minimal* window.onerror/window.onunhandledrejection shim first and only dynamically import the full SDK once an error actually fires, to buffer-and-replay it. This can partially work, but it means maintaining a second, hand-rolled error-capture surface just to compensate for not eager-loading the real one — more moving parts, more places for the buffer-and-replay logic itself to have gaps (e.g. it typically only covers uncaught exceptions and unhandled rejections, not framework-internal errors handled through other paths). If you find yourself building this shim, that's a signal you should just eager-load the SDK instead.
- Trusting "SDK is in the bundle" as proof it's working. Bundle presence, correct DSN, and open network path are necessary but not sufficient. All three can be true while zero events arrive, because none of them proves the *timing* of handler registration. This is the same category of failure as Application INFO logs vanish because nobody configured the root logger — the web server only configures its own: a signal-producing component is present and technically correct, but nothing is actually wired to receive its output at the moment that matters.
- Assuming the fix is purely code-side. Getting error capture right (eager init) is necessary but doesn't automatically mean the events landing in the dashboard are clean — PII scrubbing for an error-tracking pipeline needs its own separate check, at two different levels (payload vs. ingestion-side), see Scrubbing PII from an error-tracking tool needs two separate fixes — code AND platform setting. Fixing the capture gap and fixing the privacy posture are two independent pieces of work; neither substitutes for the other.
Verified against
18 claims checked against these sources
What links here
Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.