Skip to content

Client navigation leaves stale +error.svelte mounted after remote query render error with experimental handleRenderingErrors #15694

Description

@tudor-cel-stan

Describe the bug

I’m using SvelteKit with both kit.experimental.remoteFunctions and kit.experimental.handleRenderingErrors enabled.

When a remote query throws an error(404, ...) during component render on a client-rendered page, the nearest +error.svelte renders as expected for that route. However, after navigating client-side to another route, the failed error UI is not torn down correctly and remains mounted above the next page’s content.

This minimal repro has two routes:

  • / renders a normal home page with a link to /data
  • /data awaits a remote query at the top level of +page.svelte

The remote query throws:

error(404, "There's no data here");

Reproduction

https://github.com/tudor-cel-stan/sveltekit-error-page-navigation-repro

Steps:

  1. bun install
  2. bun run dev
  3. Open /
  4. Click Go to error page
  5. Confirm the error page renders for /data
  6. Click Click this to see the issue
  7. Observe that / renders, but the old error page remains mounted above the home page content

Relevant files in the repro:

  • src/routes/data.remote.ts
  • src/routes/data/+page.svelte
  • src/routes/+error.svelte
  • src/routes/+page.svelte
  • svelte.config.js

Logs

Browser console:
- No especially useful client error is emitted in the repro
- The issue is primarily visible in the rendered DOM: the old `+error.svelte` remains mounted after navigation

Server logs:
- Only the expected 404 caused by the remote query throwing `error(404, "There's no data here")`
- No additional server-side crash or fatal exception

System Info

System:
  OS: macOS 26.3.1
  CPU: (8) arm64 Apple M3
  Memory: 1.24 GB / 16.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 24.13.0 - /Users/tudorcelstan/.nvm/versions/node/v24.13.0/bin/node
  npm: 11.6.2 - /Users/tudorcelstan/.nvm/versions/node/v24.13.0/bin/npm
  pnpm: 10.33.0 - /Users/tudorcelstan/Library/pnpm/pnpm
  bun: 1.3.10 - /Users/tudorcelstan/.bun/bin/bun
Browsers:
  Brave Browser: 145.1.87.190
  Chrome: 146.0.7680.178
  Safari: 26.3.1
npmPackages:
  @sveltejs/adapter-auto: ^7.0.1 => 7.0.1
  @sveltejs/kit: ^2.57.1 => 2.57.1
  @sveltejs/vite-plugin-svelte: ^7.0.0 => 7.0.0
  svelte: ^5.55.2 => 5.55.2
  vite: ^8.0.8 => 8.0.8

Severity

annoyance

Additional Information

Related issues / nearby prior art I found:

I don’t think those exactly match this repro.

What seems distinct here is:

  • the remote query error is caught and rendered
  • client navigation to another route succeeds
  • but the old failed route UI is still left mounted in the page

A full page reload clears the bad state.
A client-side internal navigation does not.

Activity

  1. added theissue type on Apr 10, 2026
  2. big-mike-88 commented on May 26, 2026

    @big-mike-88

    Throwing my 2c here since this affects me as well. For anyone encountering this, until the devs fix it, here's a temporary workaround on an index-level layout

    		{#key page.url.pathname}
    			{@render children()}
    		{/key}

    Forcing svelte to re-render pages will fix the issue. REMEMBER TO MARK THIS WITH A TODO or something that generates code smells in whatever tool you use. This likely is quite terrible for performance

  3. hanszoons commented on Jun 29, 2026

    @hanszoons
    Contributor

    Hit this too — same combo (remoteFunctions + handleRenderingErrors, render-time error() from an await-ed remote query). Confirmed on @sveltejs/kit@2.68.0 / svelte@5.56.4. Worth adding the root cause, since it isn't analysed above:

    .svelte-kit/generated/root.svelte wraps each route layer in a <svelte:boundary>, and a failed Svelte 5 boundary stays failed until reset() is called — a prop change alone doesn't re-render the main content. On each client navigation the router clears its own tracking var and calls root.$set(...), but never reset()s the boundary (nor is the boundary {#key}-ed on the route):

    // @sveltejs/kit/src/runtime/client/client.js (navigate flow)
    rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
    root.$set(navigation_result.props);

    So the failed boundary survives every client navigation. The first load is fine because it goes through initialize(), which creates the root fresh. Load errors (error() in a load) are unaffected because they go through the router, which resets normally.

    Possible fix: reset() the failed boundary on client navigation, or {#key} it on the route id. The existing // TODO ... rethink for SvelteKit 3 comment sits right at this line.

  4. exentrich commented on Jul 8, 2026

    @exentrich

    Same root cause, different manifestation — traced it in the source; sharing a repro that doesn't need remote functions.

    Root cause: with handleRenderingErrors the generated root component wraps each layout depth in <svelte:boundary>. Once a boundary fails, nothing ever resets it: client navigation only calls root.$set(...) (runtime/client/client.js), and a failed boundary ignores prop updates — per svelte semantics only reset() exits the failed state, and kit never calls it. Meanwhile page.status/page.error are updated by the successful navigation, so the stuck +error.svelte re-renders with status: 200 while nothing is actually failing — console and server logs stay clean.

    Minimal repro — handleRenderingErrors: true + compilerOptions.experimental.async:

    <!-- routes/+page.svelte -->
    <a href="/broken">break</a>
    
    <!-- routes/broken/+page.svelte -->
    <script>
        import { error } from '@sveltejs/kit'
        async function load() { error(404, 'nope') }
    </script>
    {await load()}
    
    <!-- routes/+error.svelte -->
    <script>import { page } from '$app/state'</script>
    <h1>status: {page.status}</h1>
    <a href="/">go home</a>
    1. Open /, click break → error page, status: 404 ✅
    2. Click go home → URL changes to /, but the error page stays mounted, now showing status: 200 ❌. Every further client-side navigation is trapped; only a full reload escapes.

    Impact: any +error.svelte containing links (e.g. a 401 page with a "Sign in" button) traps the user. Workarounds all fall short: beforeNavigate + location.href corrupts history on back/forward traversals; {#key page.url.pathname} around layout children remounts the whole app on every navigation and can't cover the root-level boundary. Looks like kit needs to reset() its boundaries when a navigation commits.

    kit 2.69.1, svelte 5.56.4

  5. added this to the 3.0 milestone on Jul 8, 2026
  6. added a commit that references this issue on Jul 10, 2026
    f76d7d9
  7. teemingc commented on Jul 10, 2026

    @teemingc
    Member

    Closed by #16296

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions