Describe the bug
Since 5.25, deriveds declared with let can be reassigned; the docs describe the lifetime as
Derived expressions are recalculated when their dependencies change, but you can temporarily override their values by reassigning them
For a .svelte.ts module compiled with client codegen this holds in every context I measured (owned and unowned, flat and chained). Under server codegen the documented lifetime does not exist: after the reassignment the derived is never evaluated again, so the override survives every dependency change, permanently.
// store.svelte.ts — universal code, runs during SSR and on the client
let seed = $state(1);
let d = $derived.by(() => seed * 10);
d; // 10
d = 999; // override
seed = 2; // dependency change
d; // client build: 20 (documented) · server build: 999, forever
That means a shared rune store (the "universal reactivity" use case) has two different override semantics depending on which build executes it — e.g. an optimistic-UI override applied during SSR is never corrected server-side — and nothing in the docs mentions a difference.
The practical sharp edge is that test setups fall into the server world unnoticed. With Vitest, environment: 'node' gives you the SSR transform (server codegen). And the jsdom default without resolve.conditions: ['browser'] produces a third, mixed state: client codegen but the server top-level svelte entry, where override semantics are client-like but flushSync is a silent no-op and $effect.root effects never fire. We first noticed all this as the same test sequence yielding contradictory results in two files of one repo.
Related, but not the same: #15934 / #15414 cover unowned per-read re-evaluation in the client build — that behaviour does not break the documented override lifetime; the server-codegen divergence reported here is a separate, undocumented one.
Question: is the server behaviour intended (i.e. override lifetime is only specified for the client build)? If so, could the docs state that overrides are permanent under SSR/server codegen — and should reassigning a derived in server code perhaps warn in DEV?
Reproduction
Repository-free repro (a REPL cannot show this — the playground always runs mounted client code, which is exactly the one world that matches the docs):
https://github.com/urbicon/svelte-derived-override-server-codegen — a minimal vitest project with three projects (node env → server codegen; jsdom default → client codegen + server entry; jsdom with resolve.conditions: ['browser'] → full client). bun install && bunx vitest run prints the matrix; the README carries the full result table and the ~20-line scenario module.
| context |
codegen |
override after dependency change |
derived eval count |
| node env, flat |
server |
999 — survives forever |
1 (never re-evaluated after override) |
| node env, chained |
server |
survives (through the chain) |
inner 1 / outer 4 |
| jsdom default, flat/chained/owned |
client (+ server entry) |
20 / 21 — documented |
2 |
| jsdom + browser condition, flat/chained/owned |
client |
20 / 21 — documented |
2 |
Logs
No errors or warnings are emitted in any of the cases — the divergence is silent.
System Info
- svelte 5.56.10, @sveltejs/vite-plugin-svelte 7.3.0, vitest 4.1.11, jsdom 30.0.1
- macOS (darwin arm64), node v25.2.1
Severity
annoyance
Describe the bug
Since 5.25, deriveds declared with
letcan be reassigned; the docs describe the lifetime asFor a
.svelte.tsmodule compiled with client codegen this holds in every context I measured (owned and unowned, flat and chained). Under server codegen the documented lifetime does not exist: after the reassignment the derived is never evaluated again, so the override survives every dependency change, permanently.That means a shared rune store (the "universal reactivity" use case) has two different override semantics depending on which build executes it — e.g. an optimistic-UI override applied during SSR is never corrected server-side — and nothing in the docs mentions a difference.
The practical sharp edge is that test setups fall into the server world unnoticed. With Vitest,
environment: 'node'gives you the SSR transform (server codegen). And the jsdom default withoutresolve.conditions: ['browser']produces a third, mixed state: client codegen but the server top-levelsvelteentry, where override semantics are client-like butflushSyncis a silent no-op and$effect.rooteffects never fire. We first noticed all this as the same test sequence yielding contradictory results in two files of one repo.Related, but not the same: #15934 / #15414 cover unowned per-read re-evaluation in the client build — that behaviour does not break the documented override lifetime; the server-codegen divergence reported here is a separate, undocumented one.
Question: is the server behaviour intended (i.e. override lifetime is only specified for the client build)? If so, could the docs state that overrides are permanent under SSR/server codegen — and should reassigning a derived in server code perhaps warn in DEV?
Reproduction
Repository-free repro (a REPL cannot show this — the playground always runs mounted client code, which is exactly the one world that matches the docs):
https://github.com/urbicon/svelte-derived-override-server-codegen — a minimal vitest project with three projects (node env → server codegen; jsdom default → client codegen + server entry; jsdom with
resolve.conditions: ['browser']→ full client).bun install && bunx vitest runprints the matrix; the README carries the full result table and the ~20-line scenario module.Logs
No errors or warnings are emitted in any of the cases — the divergence is silent.
System Info
Severity
annoyance