Context: implementing the standard SvelteKit stale-chunk recovery (kit.version polling + beforeNavigate full reload + handleError auto-reload + Sentry beforeSend filter) and trying to prove each layer actually works.
Three things that cost time, all confirmed against @sveltejs/kit 2.x source and a live adapter-node build:
Version polling does not exist in dev. In
src/exports/vite/index.js, the__SVELTEKIT_APP_VERSION_POLL_INTERVAL__define is set tos(kit.version.pollInterval)only whenis_build; the dev branch hardcodes'0', and_app/version.jsonis never emitted by the dev server (it is afileNamein the build's emitFile call). Sofetch('/_app/version.json')404s in dev and$updatednever flips. Any verification plan that says 'save a file in dev, watch the other tab reload' is testing nothing. Verify instead by (a) grepping the built client chunk for the poll timer (const n=6e4next to the_app/version.jsonfetch) and (b) runningnode build/index.jsand driving a real browser.adapter-node precompress makes a hand-edited version.json invisible. To simulate a deploy I overwrote
build/client/_app/version.json, but the browser kept receiving the old string whilecurlsaw the new one. Cause: the build also emitsversion.json.brandversion.json.gz, and sirv serves the precompressed variant to any client sendingAccept-Encoding: br. curl sends none. Delete (or regenerate) the.br/.gzsiblings and restart the server, otherwise the skew never reaches the client. Also restart after editing: sirv builds its file table at startup.Injecting a realistic chunk 404 is easy with request interception, and it distinguishes your handler from SvelteKit's built-in. In puppeteer: intercept,
req.respond({status: 404})for/_app/immutable/nodes/*.js, then trigger a client-side nav. Keepversion.jsonmatching the loaded build while doing this, otherwisestores.updated.check()inside kit's load-error path (client.js, theReferenced node could have been removed due to redeploybranch) does its ownnative_navigationand masks whatever yourhandleErrorhook does. Set awindow.__sentinelbefore the click: if it survives, no full page load happened.
Bonus, confirmed empirically on @sentry/sveltekit 10.70: the mechanism on errors routed through handleError is {type: 'auto.function.sveltekit.handle_error', handled: true}. A beforeSend filter must substring-test for 'sveltekit'; mechanism.type === 'sveltekit' never matches and is silently dead code.