Describe the bug
Under compilerOptions.experimental.async, declaring {const value = $derived(await asyncFn())} inside a {#snippet} body and then reading value inside a nested {#snippet} via a synchronous $derived crashes during SvelteKit SSR with TypeError: value is not a function. The client compile is valid and svelte-check stays green; the crash is SSR-only.
My questions:
- Is this a bug, or by design (i.e. an async
$derived in a snippet body can never be closed over by a nested snippet's sync $derived at SSR)?
- If by design — what's the idiomatic way to share an awaited value with nested snippets?
Reproduction
Hit / in the preview — 500 immediately.
src/routes/+page.svelte:
<script>
async function getValue() {
return new Set(['a', 'b', 'c']);
}
let override = $state(undefined);
</script>
{#snippet outer()}
{const value = $derived(await getValue())}
{#snippet inner(keys)}
{const all_present = $derived(keys.every((k) => (override ?? value).has(k)))}
<p>all present: {all_present}</p>
{/snippet}
{@render inner(['a', 'b'])}
{@render inner(['a', 'x'])}
{/snippet}
{@render outer()}
svelte.config.js:
import adapter from '@sveltejs/adapter-auto';
const config = {
kit: { adapter: adapter() },
compilerOptions: {
experimental: { async: true },
},
};
export default config;
Note: reading value directly in inner (e.g. <p>{value.has('a')}</p>) does not crash — the compiler wraps that read in $$renderer.async. The crash only triggers when value is read through a nested sync $derived.
Workaround
Pass await fn() as a snippet parameter; nested snippets then read it via the parameter closure, which works fine on SSR:
{#snippet outer()}
{#snippet inner(value, keys)}
{const all_present = $derived(keys.every((k) => (override ?? value).has(k)))}
<p>all present: {all_present}</p>
{/snippet}
{@render inner(await getValue(), ['a', 'b'])}
{@render inner(await getValue(), ['a', 'x'])}
{/snippet}
{@render outer()}
Logs
[500] GET /
TypeError: value is not a function
at src/routes/+page.svelte:10:9
at Array.every (<anonymous>)
at src/routes/+page.svelte:12:38
at .../svelte/src/internal/server/index.js:445:12
at .../svelte/src/internal/server/index.js:498:28
at inner (src/routes/+page.svelte:12:10)
at outer (src/routes/+page.svelte:15:11)
`
Line 10 = {const all_present = $derived(keys.every(...))}, line 12 = <p>all present: {all_present}</p>.
System Info
System: macOS 15.7.7, Apple M1 Pro (arm64)
Binaries: Node 24.17.0, pnpm 11.8.0
Packages: svelte 5.56.4, @sveltejs/kit 2.68.0, vite 8.1.0
Severity
annoyance
Describe the bug
Under
compilerOptions.experimental.async, declaring{const value = $derived(await asyncFn())}inside a{#snippet}body and then readingvalueinside a nested{#snippet}via a synchronous$derivedcrashes during SvelteKit SSR withTypeError: value is not a function. The client compile is valid andsvelte-checkstays green; the crash is SSR-only.My questions:
$derivedin a snippet body can never be closed over by a nested snippet's sync$derivedat SSR)?Reproduction
Hit
/in the preview — 500 immediately.src/routes/+page.svelte:svelte.config.js:Note: reading
valuedirectly ininner(e.g.<p>{value.has('a')}</p>) does not crash — the compiler wraps that read in$$renderer.async. The crash only triggers whenvalueis read through a nested sync$derived.Workaround
Pass
await fn()as a snippet parameter; nested snippets then read it via the parameter closure, which works fine on SSR:{#snippet outer()} {#snippet inner(value, keys)} {const all_present = $derived(keys.every((k) => (override ?? value).has(k)))} <p>all present: {all_present}</p> {/snippet} {@render inner(await getValue(), ['a', 'b'])} {@render inner(await getValue(), ['a', 'x'])} {/snippet} {@render outer()}Logs
System Info
Severity
annoyance