Repository navigation
svelte.config.js version changes cause most client chunks to generate a new hash despite no other changes #12260
Description
Updated bug report
Changing the version in svelte.config.js and rebuilding the exact same code results in most client chunks also being updated with a new hash. The cause seems to be the version check code being updated to look for a new version, and then most client chunks directly or indirectly importing that change.
Initial bug report
Describe the bug
If you build the same project twice, you get different files out even though nothing has changed.
I was trying to break my project up using manualChunks to create a vendor.js file with the hope that while we're deploying multiple times a day, our dependencies rarely change so clients would reuse many of the same js files they'd already have cached from previous visits. In practice this doesn't work because some base level thing that many pieces inherit from is constantly changing so you never get the same vendor.js file out between builds.
The issue seems to be around a randomly generated key in the output like globalThis.__sveltekit_zp1tl where that hash changes every build. That shows up as the main difference in entry.{hash}.js which many other files then import.


I thought this should work since Richard Harris actually raised this bug with Vite: vitejs/vite#11911
Reproduction
pnpm create svelte@latest deterministic-build- press enter a bunch. none of these settings matter. Same issue appears in Typescript vs JS, adding eslint, etc, or even doing it in npm instead of pnpm
cd deterministic-buildpnpm ipnpm build. copy outputpnpm build. copy output- compare outputs. Notice some files change
Logs
> deterministic-build@0.0.1 build
> vite build
vite v5.2.11 building SSR bundle for production...
"confetti" is imported from external module "@neoconfetti/svelte" but never used in "src/routes/sverdle/+page.svelte".
✓ 109 modules transformed.
vite v5.2.11 building for production...
✓ 92 modules transformed.
.svelte-kit/output/client/_app/version.json 0.03 kB │ gzip: 0.05 kB
.svelte-kit/output/client/.vite/manifest.json 7.12 kB │ gzip: 0.99 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-greek-ext-400-normal.CsqI23CO.woff2 7.51 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-cyrillic-400-normal.36-45Uyg.woff2 9.10 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-greek-400-normal.C3zng6O6.woff2 10.52 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-latin-ext-400-normal.D6XfiR-_.woff2 11.36 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-cyrillic-ext-400-normal.B04YIrm4.woff2 15.77 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-latin-400-normal.DKjLVgQi.woff2 16.28 kB
.svelte-kit/output/client/_app/immutable/assets/fira-mono-all-400-normal.B2mvLtSD.woff 77.36 kB
.svelte-kit/output/client/_app/immutable/assets/svelte-welcome.0pIiHnVF.webp 115.47 kB
.svelte-kit/output/client/_app/immutable/assets/svelte-welcome.VNiyy3gC.png 360.81 kB
.svelte-kit/output/client/_app/immutable/assets/5.CU6psp88.css 0.80 kB │ gzip: 0.35 kB
.svelte-kit/output/client/_app/immutable/assets/2.Cs8KR-Bb.css 1.47 kB │ gzip: 0.53 kB
.svelte-kit/output/client/_app/immutable/assets/4.DOkkq0IA.css 3.78 kB │ gzip: 1.06 kB
.svelte-kit/output/client/_app/immutable/assets/0.CT0x_Q5c.css 5.15 kB │ gzip: 1.66 kB
.svelte-kit/output/client/_app/immutable/chunks/index.R8ovVqwX.js 0.03 kB │ gzip: 0.05 kB
.svelte-kit/output/client/_app/immutable/entry/start.kuqVoxG5.js 0.07 kB │ gzip: 0.08 kB
.svelte-kit/output/client/_app/immutable/chunks/stores.CyV73Wv9.js 0.23 kB │ gzip: 0.17 kB
.svelte-kit/output/client/_app/immutable/chunks/index.Ice1EKvx.js 0.51 kB │ gzip: 0.34 kB
.svelte-kit/output/client/_app/immutable/nodes/1.DgAi-rhN.js 0.84 kB │ gzip: 0.52 kB
.svelte-kit/output/client/_app/immutable/nodes/3.BqQOub2U.js 1.57 kB │ gzip: 0.96 kB
.svelte-kit/output/client/_app/immutable/chunks/scheduler.Dk-snqIU.js 2.25 kB │ gzip: 1.05 kB
.svelte-kit/output/client/_app/immutable/nodes/5.CwxmUzn6.js 2.35 kB │ gzip: 1.11 kB
.svelte-kit/output/client/_app/immutable/nodes/2.BMQFqo-e.js 5.48 kB │ gzip: 2.60 kB
.svelte-kit/output/client/_app/immutable/chunks/index.DDRweiI9.js 6.09 kB │ gzip: 2.57 kB
.svelte-kit/output/client/_app/immutable/entry/app.D6zuvAPi.js 6.64 kB │ gzip: 2.65 kB
.svelte-kit/output/client/_app/immutable/nodes/0.qacZO6So.js 8.77 kB │ gzip: 3.57 kB
.svelte-kit/output/client/_app/immutable/nodes/4.D_o_hs5U.js 17.08 kB │ gzip: 7.07 kB
.svelte-kit/output/client/_app/immutable/chunks/entry.DI0Mx0P3.js 27.97 kB │ gzip: 10.97 kB
✓ built in 248ms
.svelte-kit/output/server/.vite/manifest.json 7.17 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-greek-ext-400-normal.CsqI23CO.woff2 7.51 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-cyrillic-400-normal.36-45Uyg.woff2 9.10 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-greek-400-normal.C3zng6O6.woff2 10.52 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-latin-ext-400-normal.D6XfiR-_.woff2 11.36 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-cyrillic-ext-400-normal.B04YIrm4.woff2 15.77 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-latin-400-normal.DKjLVgQi.woff2 16.28 kB
.svelte-kit/output/server/_app/immutable/assets/fira-mono-all-400-normal.B2mvLtSD.woff 77.36 kB
.svelte-kit/output/server/_app/immutable/assets/svelte-welcome.0pIiHnVF.webp 115.47 kB
.svelte-kit/output/server/_app/immutable/assets/svelte-welcome.VNiyy3gC.png 360.81 kB
.svelte-kit/output/server/_app/immutable/assets/_page.CU6psp88.css 0.80 kB
.svelte-kit/output/server/_app/immutable/assets/_page.BBloWWo2.css 1.45 kB
.svelte-kit/output/server/_app/immutable/assets/_page.DOkkq0IA.css 3.78 kB
.svelte-kit/output/server/_app/immutable/assets/_layout.c5uvFHuT.css 5.40 kB
.svelte-kit/output/server/chunks/prod-ssr.js 0.04 kB
.svelte-kit/output/server/entries/pages/_page.ts.js 0.05 kB
.svelte-kit/output/server/chunks/index3.js 0.08 kB
.svelte-kit/output/server/entries/pages/about/_page.ts.js 0.13 kB
.svelte-kit/output/server/entries/pages/sverdle/how-to-play/_page.ts.js 0.13 kB
.svelte-kit/output/server/chunks/client.js 0.28 kB
.svelte-kit/output/server/internal.js 0.31 kB
.svelte-kit/output/server/entries/fallbacks/error.svelte.js 0.47 kB
.svelte-kit/output/server/chunks/stores.js 0.54 kB
.svelte-kit/output/server/entries/pages/about/_page.svelte.js 1.07 kB
.svelte-kit/output/server/chunks/index2.js 1.33 kB
.svelte-kit/output/server/chunks/index.js 2.10 kB
.svelte-kit/output/server/chunks/ssr.js 4.00 kB
.svelte-kit/output/server/chunks/exports.js 5.96 kB
.svelte-kit/output/server/chunks/internal.js 6.03 kB
.svelte-kit/output/server/entries/pages/sverdle/how-to-play/_page.svelte.js 6.47 kB
.svelte-kit/output/server/entries/pages/_page.svelte.js 13.48 kB
.svelte-kit/output/server/entries/pages/_layout.svelte.js 14.67 kB
.svelte-kit/output/server/entries/pages/sverdle/_page.svelte.js 23.42 kB
.svelte-kit/output/server/index.js 90.30 kB
.svelte-kit/output/server/entries/pages/sverdle/_page.server.ts.js 146.12 kB
✓ built in 1.01s
Run npm run preview to preview your production build locally.
> Using @sveltejs/adapter-auto
Could not detect a supported production environment. See https://kit.svelte.dev/docs/adapters to learn how to configure your app to run on the platform of your choosing
✔ doneSystem Info
System:
OS: macOS 14.4.1
CPU: (16) arm64 Apple M3 Max
Memory: 2.59 GB / 64.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 21.6.0 - /usr/local/bin/node
npm: 10.3.0 - /usr/local/bin/npm
pnpm: 9.1.0 - ~/Library/pnpm/pnpm
Browsers:
Chrome: 125.0.6422.78
Safari: 17.4.1
npmPackages:
@sveltejs/adapter-auto: ^3.0.0 => 3.2.1
@sveltejs/kit: ^2.0.0 => 2.5.10
@sveltejs/vite-plugin-svelte: ^3.0.0 => 3.1.0
svelte: ^4.2.7 => 4.2.17
vite: ^5.0.3 => 5.2.11Severity
serious, but I can work around it
Additional Information
No response
Activity
Conduitry commentedon May 24, 2024
You need to set https://kit.svelte.dev/docs/configuration#version to something consistent; otherwise, it uses the current timestamp, which changes every build, affecting the hashes.
rory-orennia commentedon May 24, 2024
@Conduitry Ohh thank you! That was definitely it. I guess now my concern is, how is this supposed to work in the case of making a vendor.js file? Unless I hardcode the version to something and then ignore the svelte version handling, I'm still going to get a different vendor.js file every time I do a new commit (assuming you tie version to git like is suggested).
I think even if I manually pulled this version check out into another chunk, it would be referenced by most other client chunks in the app and thus they would also change their hash.
rory-orennia commentedon May 24, 2024
Here's me changing the svelte.config.js -> version -> name from 1 to 2 and it basically changing every client chunk

mquandalle commentedon May 27, 2024
If two compiled files across two distincts app versions are exactly the same, why not keep the same file name to avoid unnecessary cache invalidation on the client?
rory-orennia commentedon May 27, 2024
That's the problem. They aren't exactly the same because some base level sveltekit code gets changed, and then most client code chunks import that change, which causes them to change because the hash they reference (eg. import somebasefile.constantlychanginghash.js) has changed.
If the cause of all of this is the version check code, I think we need to find a way to make that code not bleed into every client chunk
rory-orennia commentedon May 27, 2024
@eltigerchino @Conduitry Do you want me to close this ticket and open a new one that is the new problem? (Version changes cause most client chunks to generate a new hash despite no other changes). Or do you want me to rename this one instead?
teemingc commentedon May 27, 2024
Yes, renaming this one would be great. Thanks!
[-]SvelteKit builds are non-deterministic[/-][+]svelte.config.js version changes cause most client chunks to generate a new hash despite no other changes[/+]matindow commentedon Jun 15, 2024
Are there any proposals/thoughts for how to address this?
How much worse would it be if the content in
version.jsonwere replaced with something like a junction map of component names => file paths that all of the chunks referenced dynamically? I am assuming there are a lot of really great/important benefits to being able to hardcode a static file import into all of the chunks that make this a non starter?I'm trying to think through other ways of doing this version check, and everything seems to come back to the same frictions.
I do agree that incrementing the version number should intuitively not cascade into a new hash for unchanged files in your project, though. Seems not great even aside from the terrible caching issues.
Revert "fix: replace version in generateBundle (#12700)"
Revert "fix: replace version in generateBundle (#12700)" (#12779)
version.name#14176jippi commentedon Jun 20, 2026
This is a solution I came up with and running as experiment - summary by Claude because its 1am and I'm le tired
1. Constant sentinel for
kit.version.nameNow
version_hashis constant →globalThis.__sveltekit_<hash>is identical every deploy, and the bundledversionstring is constant → immutable chunks are byte-stable.2. Override the client-only
versiondefine to read a<meta>at runtimeThe bundled
versionmust reflect the real build at runtime without baking the SHA. Override the client define so it reads a<meta>tag (constant code → no SHA in bytes):$app/environment'sversion(and thus kit'supdatedcheck, your analytics, etc.) is now the real SHA at runtime, read once at module-eval from the<meta>parsed in<head>before any module script runs. Leave the server define alone.3. Inject the real SHA into that
<meta>per requestA
<meta>, not an inline<script>: a script injected viatransformPageChunkruns after SvelteKit computed its CSP nonce, so a strictscript-srcwould block it;<meta>is not governed byscript-src.4. Swap the sentinel → real SHA in the two NON-immutable outputs
_app/version.json(theupdatedpoll target) andservice-worker.js(precache-${version}) carry the sentinel and DO need the real SHA. Both are served no-cache / revalidated — not immutable — so rewriting them is safe. Do it in a VitecloseBundleplugin (sovite build≡pnpm build— no separate post-build step to forget), gated on the SSR pass so it runs after the adapter assemblesbuild/:Never touch
build/client/_app/immutable/**— keeping the SHA out of those bytes is the entire point.Why this is correct
updated/reload-on-deploy keeps working, unchanged: the polledversion.json(real SHA) is compared against the clientversion(real SHA from the<meta>) — real-vs-real. After a new deploy they differ →updated.currentflips → reload on next nav. No custom detector needed.Verify
In the browser: the
<meta>carries the real SHA;version.jsonmatches it; deploying a new build triggers exactly one reload-on-next-nav with no reload loop.Gotchas (each one bit us)
replaceAll, notreplace.String.replace(str, …)swaps only the first occurrence. If anything else on the page contains the marker (e.g. an explanatory HTML comment above the<meta>), it absorbs the swap and the real attribute stays raw → the client reads the literal"%app_version%"→ permanentversion.json != "%app_version%"mismatch → reload on every navigation. UsereplaceAlland don't put the marker in a comment.versionserver-side would otherwise readdocument(undefined).<meta>, not an injected inline<script>.Applicability
Single SvelteKit app (no multi-app embedding on one page — a constant
globalThis.__sveltekit_<hash>namespace is only a collision risk there). Tested with rolldown-vite; the same shape works on Rollup-Vite. Until SvelteKit ships a first-class fix for #12260, this keeps your immutable assets genuinely immutable across deploys.michael-haberzettel commentedon Jun 21, 2026
@jippi
Du you tried to check the preload mechanism: https://svelte.dev/tutorial/kit/preload
Some of my attempts failed because of this system.
To test it, you can do these steps:
jippi commentedon Jun 21, 2026
@michael-haberzettel I use the preload system extensively ( https://mangabaka.org/ ) and haven't seen any errors - since my patch above doesn't change anything in svelte, other than the "source of the version value" it shouldn't really impact any behavior in svelte either - at least i haven't noticed anything the last 8-12 development hours since I put this online
michael-haberzettel commentedon Jun 21, 2026
I will give it a try on next days 👍
fix: preserve shared client chunk hashes when the app version changes (…
teemingc commentedon Jul 16, 2026
Closed by #16324