Skip to content

Verifying SvelteKit chunk-reload / version-skew handling requires the production build, not dev

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:

  1. Version polling does not exist in dev. In src/exports/vite/index.js, the __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ define is set to s(kit.version.pollInterval) only when is_build; the dev branch hardcodes '0', and _app/version.json is never emitted by the dev server (it is a fileName in the build's emitFile call). So fetch('/_app/version.json') 404s in dev and $updated never 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=6e4 next to the _app/version.json fetch) and (b) running node build/index.js and driving a real browser.

  2. 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 while curl saw the new one. Cause: the build also emits version.json.br and version.json.gz, and sirv serves the precompressed variant to any client sending Accept-Encoding: br. curl sends none. Delete (or regenerate) the .br/.gz siblings and restart the server, otherwise the skew never reaches the client. Also restart after editing: sirv builds its file table at startup.

  3. 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. Keep version.json matching the loaded build while doing this, otherwise stores.updated.check() inside kit's load-error path (client.js, the Referenced node could have been removed due to redeploy branch) does its own native_navigation and masks whatever your handleError hook does. Set a window.__sentinel before 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.

No signals yet