fix: properly hydrate already-resolved async blocks (alternative)
#17641
Closing issues
Describe the bug
Since v5.43.0, some items in an {#each} block are duplicating in a specific scenario related to the experimental async syntax. All awaited items except the first appear a second time (eg. item 1, item 2, item 3, item 2, item 3). The items are not duplicated in the SSR ouput (in a SvelteKit app), the duplicated items are only added on client load, it appears.
Note: This only occurs in a production build, not in development. No errors/warnings are logged. The issue does not occur in v5.42.3 or below.
Reproduction
The following is a minimal reproduction of an issue I am encountering in a real app. The issue does not occur if const another = {...} (L5) is moved before const messages = await ... (L4), if another is not passed to the component, or if the <p>{message}</p> is defined directly in the {#each} block, instead of in a seperate component.
(you need to use it with hydration locally in the playground to reproduce; for an in-browser reproduction see https://stackblitz.com/edit/sveltejs-kit-template-default-es5urw13?file=src%2Froutes%2F%2Bpage.svelte)
Logs
System Info
System:
OS: Linux 5.0 undefined
CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Memory: 0 Bytes / 0 Bytes
Shell: 1.0 - /bin/jsh
Binaries:
Node: 20.19.1 - /usr/local/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 10.8.2 - /usr/local/bin/npm
pnpm: 8.15.6 - /usr/local/bin/pnpm
npmPackages:
svelte: 5.43.0 => 5.43.0
Severity
blocking an upgrade
Describe the bug
TL;DR: Using experimental async ($derived(await ...)) in a component that also uses {@attach} with a function from bind:this breaks SSR hydration. In development, this causes a warning. In production build with nested components, the application crashes completely and the page fails to render.
The issue appears to be that the async boundary interacts incorrectly with the {@attach} directive during hydration. When the component contains $derived(await ...), the hydration process seems to lose track of the attachment state, causing a mismatch between server-rendered HTML and client-side component tree. With nested {@render children?.()} calls, this mismatch escalates into a fatal TypeError that prevents the component from mounting.
Conditions to reproduce
All of the following must be present:
-
Experimental async enabled in
svelte.config.js:compilerOptions: { experimental: { async: true } } -
$derived(await ...)in the component (even if the value is not used in markup) -
{@attach ref?.action}where:
refis obtained viabind:thisfrom another component- The function is exported from that component
-
SSR enabled (default in SvelteKit)
-
Nested components with
{@render children?.()}— for TypeError to occur, at least two levels of nesting required
Behavior
| Environment | Single nesting | Double nesting |
|---|---|---|
| Dev | hydration_mismatch warning | hydration_mismatch warning |
| Production | hydration_mismatch warning | Application crashes, component fails to render |
Notes
- Removing $derived(await ...) — no error
- Removing {@attach} — no error
- Removing
bind:thisreference (using local function instead) — no error - Disabling SSR — no error
Expected
No hydration mismatch when the async value is not used in the SSR-rendered markup, and no TypeError regardless of component nesting depth.
Reproduction
svelte.config.js
export default {
compilerOptions: {
experimental: { async: true }
}
}
index.svelte
<script>
import Outer from './Outer.svelte'
import Inner from './Inner.svelte'
import Trigger from './Trigger.svelte'
const data = $derived(await Promise.resolve(['a', 'b']))
let trigger = $state()
</script>
<Outer>
<Inner {@attach trigger?.action}>
foo
</Inner>
</Outer>
<Trigger bind:this={trigger} />
Outer.svelte
<script>
let { children } = $props()
</script>
<div>{@render children?.()}</div>
Inner.svelte
<script>
let { children } = $props()
</script>
<div>{@render children?.()}</div>
Trigger.svelte
<script lang='ts'>
import type { Attachment } from 'svelte/attachments'
export function action(): Attachment<HTMLElement> {
return ()=>{}
}
</script>
Logs
https://svelte.dev/e/hydration_mismatch
TypeError: Cannot read properties of undefined (reading 'f')
System Info
System:
OS: macOS 26.2
CPU: (10) arm64 Apple M1 Pro
Memory: 147.34 MB / 16.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.13.0 - /Users/exentrich/.nvm/versions/node/v24.13.0/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 11.6.2 - /Users/exentrich/.nvm/versions/node/v24.13.0/bin/npm
bun: 1.3.7 - /Users/exentrich/.bun/bin/bun
Deno: 2.0.4 - /Users/exentrich/.deno/bin/deno
Browsers:
Brave Browser: 141.1.83.120
Chrome: 144.0.7559.110
Chrome Canary: 146.0.7666.1
Edge: 142.0.3595.80
Firefox: 146.0.1
Safari: 26.2
Safari Technology Preview: 26.0
npmPackages:
svelte: ^5.48.2 => 5.49.1
Severity
blocking an upgrade
Pull request
This is basically #17611, minus #17640, plus #17639. We need to add the $.next() call after render tags as well as components; rather than duplicating the logic, we can use is_standalone to determine when this is necessary (since this is what prevents $.append(...) from being used).
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
Info
🦋 Changeset detected
Latest commit: bb4b182
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
| Name | Type |
|---|---|
| svelte | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
pnpm add https://pkg.pr.new/svelte@17641Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)