sveltejs / sveltejs/kit

DOM reverts to pre-navigation render when same-route `goto({invalidateAll: true})` runs while a `$state`-bound attribute toggles around the await

Open
#15,875 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

router svelte 5
Dominant language
JavaScript
Stars
20.8k
Forks
2.3k
Avg merge
1d 16h
Merged PRs (30d)
156

Description

Describe the bug

After clicking a button that performs goto('/new', { invalidateAll: true }) to the same parameterized route (/fundraisers/[id]), the URL advances and reactive state updates to the new data (verified via $inspect / $effect), and the DOM briefly renders the new content. Then, within ~50 ms, the DOM is rolled back to the previous render and stays there. No further reactivity recovers it.

See Trigger Set below for all parts needed to reproduce

Trigger set (all required to reproduce)

Each of these was individually bisected; removing any of them masks the bug:

  1. compilerOptions.experimental.async: true.
  2. Same-route goto(url, { invalidateAll: true }) where the URL changes a route param.
  3. A click handler that mutates a $state to true before await goto(...), and back to false in a finally block after it resolves.
  4. A DOM attribute bound to that $state (e.g. disabled={markingReviewed}) on an element inside a control-flow block that gets torn down and rebuilt when the route data changes{#key fundraiser.id} is the simplest form, but {#if condition-derived-from-data}…{:else}…{/if} also reproduces. Removing the wrapping block (so the button is a stable DOM node across navigation) fixes it.
  5. Elsewhere on the page: a child component rendered via {#each} that contains a <svelte:boundary> with a top-level await (the awaited promise can reject; behavior is the same on pending → failed).

Notable details:

  • Inlining the <svelte:boundary> directly into +page.svelte (no child component) does not reproduce — it must be one component deep.
  • Replacing the click handler with a <form action="?/x" use:enhance> that runs the same logic reproduces too.
  • Removing the {#key} block — even keeping the disabled={markingReviewed} button — does not reproduce.
  • Removing disabled={markingReviewed} while keeping the {#key} block does not reproduce.
  • Hover preload (data-sveltekit-preload-data="hover") is not needed (this distinguishes it from sveltejs/kit#14923, which has a similar "flash then revert" signature but different trigger).
  • A $effect reading the post-navigation state fires correctly with the new value at the moment of the revert, so reactivity itself isn't broken — only DOM commit is rolling back.

What Claude thinks is happening

This looks like Svelte 5 async-coherence is treating the new render as not-yet-coherent because the new boundary's pending await is still in flight, and rolling the DOM back to the "previous coherent state" — but it never advances forward again after the boundary settles (failed snippet renders in the boundary fine; the parent DOM stays at the previous render).

The disabled={markingReviewed} binding seems to be what wires the click handler's await into the same coherence frame as the navigation, since removing it (or hoisting it out of the {#key} block) breaks the trigger. I suspect {#key} participates because it forces the new render to be a fresh subtree rather than a structural diff.

Reproduction

See this repository:

https://github.com/Narretz/svelte-async-revert

Logs

System Info
## Versions

Verified on the latest of everything as of 2026-05-19:

- `svelte`: 5.55.8
- `@sveltejs/kit`: 2.60.1
- `vite`: 8.0.13
- `@sveltejs/vite-plugin-svelte`: 7.1.2
Severity

serious, but I can work around it

Additional Information

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the linked svelte-async-revert reproduction against the listed SvelteKit, Svelte, Vite, and plugin versions, then verify the full trigger set, especially the keyed block, bound attribute, same-route goto, and nested boundary. Trace the navigation and async-boundary behavior until the DOM rollback is explained; done means the new route content remains rendered after navigation and boundary settlement.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
frontend, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.