SvelteKit: stale-tab 'Failed to fetch dynamically imported module' still reported after a deploy despite version polling
SvelteKit 2.x app (adapter-node, Vite) deployed several times a day. Sentry keeps getting TypeError: Failed to fetch dynamically imported module: https://example.com/_app/immutable/nodes/15.abc123.js (Chrome) / Importing a module script failed (Safari) from tabs opened before a deploy. Added kit.version.pollInterval plus a beforeNavigate hook that does location.href = to.url.href when $updated is true, as the docs suggest, but events continue. The error reaches handleError with mechanism auto.function.sveltekit.handle_error, or arrives as an unhandled rejection with no breadcrumbs.
SvelteKit already recovers from a failed route import during a real navigation: the client runs updated.check() and does a native reload before calling handleError (kit 2.70.3 client.js ~1354-1359). The events that still reach Sentry come from paths that never fire beforeNavigate:
- Preloads. With
data-sveltekit-preload-data="hover"(the default template), hovering/touching a link preloads the route; a failed import there goes straight tohandleErrorwithout the version check (client.js~1333-1335). - Lazy imports outside the router, e.g.
await import('posthog-js')inonMount: an unhandled rejection, no navigation at all.
Keep the beforeNavigate reload (it does keep link/goto/form navigations fresh; call cancel() before setting location.href, and skip type === 'popstate', where cancel() would history.go() back and a full load aborts the in-flight __data.json into a new TypeError: network error). Then filter stale-build noise in Sentry.init({ beforeSend }), which may return a Promise:
import { version } from '$app/environment';
const CHUNK = /Failed to fetch dynamically imported module|Importing a module script failed|error loading dynamically imported module|Unable to preload CSS for/;
beforeSend: (event) => event.exception?.values?.some((e) => CHUNK.test(e.value ?? ''))
? fetch('/_app/version.json', { cache: 'no-store' })
.then((r) => r.json()).then((j) => (j.version !== version ? null : event))
.catch(() => event)
: event,This drops the event only when the server's build differs from the tab's; a failed import on the current build is a real bug and still reports. Also drop _app/version.json fetch breadcrumbs in beforeBreadcrumb, or the 60 s poll fills every trail.