fix: exit resolved async blocks on correct node when hydrating
#17640
Closing issue
Describe the bug
Using a nested {#if}{/if} on an async component while having the experimental async feature enabled, breaks hydration when using SSR.
It seems like it fails to find the hydration markers, using whatever else it finds instead.
Reproduction
https://github.com/santiagocezar/hydration-troubles-repro
It seems to be a regression in 5.49.2. Plus, I checked if PR #17611 fixed it by chance but had no luck there, sadly.
There's three variants!
- The sync one is the boring one that just works
- The async-only one causes a hydration mismatch (but the error log doesn't give much details as to why)
- The async+onclick one causes a completely different error! (it grabs a text node for some reason)
Logs
# on the Async-only variation
[svelte] hydration_mismatch
Hydration failed because the initial UI does not match what was rendered on the server
https://svelte.dev/e/hydration_mismatch
Uncaught (in promise) Object { }
# on the Async+onclick one
Uncaught (in promise) DOMException: Node.appendChild: Cannot add children to a Text
System Info
System:
OS: Linux 6.18 cpe:/o:nixos:nixos:26.05 26.05 (Yarara)
CPU: (16) x64 AMD Ryzen 7 7730U with Radeon Graphics
Memory: 4.65 GB / 14.98 GB
Container: Yes
Shell: 0.109.1 - /run/current-system/sw/bin/nu
Binaries:
Node: 24.13.0 - /run/current-system/sw/bin/node
npm: 11.6.2 - /run/current-system/sw/bin/npm
pnpm: 10.28.0 - /run/current-system/sw/bin/pnpm
bun: 1.3.6 - /run/current-system/sw/bin/bun
Browsers:
Firefox: 147.0.1
Firefox Developer Edition: 147.0.1
npmPackages:
svelte: 5.49.2 => 5.49.2
Severity
blocking an upgrade
Pull request
Alternative to the latter part of #17611 — see #17611 (comment)
Fixes #17618
In the fast-path, it is unknown to us within $.async whether or not the marker will advance to the closing marker of $.async - with a component as its child it will (because of the $.append inside it), with an if block for example it will not (because the if block logic will stop at its closing marker). We cannot know which case it is hence we advance to the end marker before calling the inner function in $.async, to then definitly set $.async's closing marker.
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: ddce7b1
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@17640This won't work for components that are the single child of $.async. The inner component calls $.append if it has content, advancing it to the end marker of $.async already, and as such an additional hydrate_next will break hydration. So we cannot know whether or not we should advance, and we therefore need to do this somewhat costly double traversal.
Ah, good catch. I can't help but feel that this suggests something going subtly wrong somewhere else, though I'm not going to dig into it now. Left a TODO note instead, maybe we can investigate it another time
agreed 👍
fix: exit resolved async blocks on correct node when hydrating
• Feb 6, 2026, 2:50 AMPro 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 ;)