Skip to content

Teleport + Suspense unmount crash during page navigation in Nuxt SSR (regression in 3.5.31) #14640

@Gabriel-SPassos

Description

@Gabriel-SPassos

Vue version

3.5.31

Link to minimal reproduction

https://github.com/Gabriel-SPassos/vue-teleport-regression

Steps to reproduce

git clone https://github.com/Gabriel-SPassos/vue-teleport-regression
cd vue-teleport-regression
npm install
npm run dev
  1. Open http://localhost:3000
  2. Click "Go"
  3. Check the browser console

What is expected?

Navigation to Page B completes without errors. Teleported elements from the previous page are cleanly unmounted.

What is actually happening?

Dev mode (npm run dev):

RangeError: Maximum call stack size exceeded
    at queuePostFlushCb
    at queueEffectWithSuspense
    at Object.process
    at flushPostFlushCbs
    at flushJobs

Infinite recursion in the scheduler — deferred Teleport post-flush callbacks create a loop between queueEffectWithSuspense and flushJobs.

Production mode — observed in a real-world Nuxt app:

TypeError: Cannot destructure property 'bum' of 'y' as it is null.
    at unmountComponent
    at unmount
    at Object.remove (Teleport.remove)

The page freezes — URL updates but the UI does not re-render.

Vue 3.5.30: No errors in either dev or production mode. Navigation works as expected.

System Info

System:
  OS: macOS 15.5
  Node: v20.19.0
Binaries:
  npm: 10.8.2
npmPackages:
  vue: 3.5.31
  nuxt: ^4.1.1

Any additional comments?

Scenario:

  • Nuxt 4 app with SSR enabled
  • Page A (index.vue): await useFetch() in setup (triggers Suspense wrapping by <NuxtPage>), two <teleport to="body"> blocks each containing a <Transition> wrapping v-if content with nested child components, reactive state changes after navigateTo() (clearing items array + resetting a flag in finally)
  • Page B (page-b.vue): has async setup (await useFetch(...)) and its own <teleport to="body">
  • The crash occurs during the Suspense branch swap — deferred Teleport post-flush callbacks create an infinite loop in the scheduler

Candidate PRs merged between 3.5.30 and 3.5.31:

PR Description Suspicion
#9392 fix(suspense): avoid unmount activeBranch twice if wrapped in transition High
#8619 fix(runtime-dom): defer teleport mount/update until suspense resolves High
#12922 fix(suspense): update suspense vnode's el during branch self-update Medium

The interaction between #8619 (deferring Teleport operations when parentSuspense.pendingBranch exists) and #9392 (changing Suspense unmount timing when wrapped in Transition) appears to create this issue during Nuxt's page navigation cycle (Suspense + Transition + Teleport).

Workaround: Pin vue to 3.5.30:

{
  "overrides": {
    "vue": "3.5.30"
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    🔨 p3-minor-bugPriority 3: this fixes a bug, but is an edge case that only affects very specific usage.regressionscope: teleport

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions