svelte-async-hydration-repro Svelte Themes

Svelte Async Hydration Repro

Async Bug Repro

Svelte 5 async hydration teardown bug — minimal repro

When an async page (using experimental.async + SvelteKit remote functions) is server-rendered and hydrated, the first client-side navigation away from it fails to remove part of the page's DOM. The orphaned nodes are left inside the layout's {@render children()} container, stacked above the next page.

TL;DR

  • Only reproduces in a production build (build + preview). vite dev does not reproduce it.
  • Only reproduces on the first navigation away from a hydrated async page (initial full page load). Reaching the page via client-side navigation first and then leaving works correctly.
  • The page's snippet-rendering child component (Masthead) is removed, but the sibling <main> and <PageFooter> that follow it are leaked.

Run it

pnpm install
pnpm build
pnpm preview        # http://localhost:4173

Then:

  1. Open /news directly (a full page load → SSR + hydration).
  2. Click the Settings link.
  3. The old /news <main> and footer ("No algorithms, no noise…") remain on the page, stacked above the Settings content.

Expected

After navigating to /settings, .page-container contains exactly the settings page: header.page-header, main.content, footer.page-footer.

Actual

.page-container contains the leaked /news nodes followed by the settings page:

main.content        ← leaked from /news
footer.page-footer  ← leaked from /news
header.page-header  ← /settings
main.content        ← /settings
footer.page-footer  ← /settings

Required ingredients

All of these are needed to trigger the bug:

  1. compilerOptions.experimental.async: true and kit.experimental.remoteFunctions: true.
  2. A production build (the dev compiler output does not reproduce it).
  3. The page is reached via initial full page load (SSR + hydration), not client navigation.
  4. Nested async boundaries: an async page (/news/+page.svelte, awaits a remote get_sources()) renders an async child component (EditorialDesign.svelte, awaits get_editions() / get_edition()).
  5. The async child renders, in order:
    • a child component that renders a snippet (Masthead{@render ...}),
    • sibling markup after it (<main>),
    • a PageFooter component.
  6. The destination page (/settings) renders the same top-level component shape (a Masthead-produced <header>, a <main class="content">, and the same PageFooter component) in the same order.

Removing any one of these (e.g. reaching /news via client nav, or making the page non-async, or not sharing the PageFooter/Masthead shape with the destination) makes the leak disappear.

Versions

  • svelte 5.55.9
  • @sveltejs/kit 2.61.1
  • @sveltejs/vite-plugin-svelte 6.2.4
  • vite 7.3.3
  • @sveltejs/adapter-node 5.x

Notes

This mirrors a real application where every authenticated page is async (each awaits a remote function) and shares a common Masthead + PageFooter chrome. The first navigation after landing on the news page leaves a duplicated, dead copy of the news <main> + footer in the DOM.

Top categories

Loading Svelte Themes