Skip to content

[Bug] keep-alive: double unmount (parentNode null / bum null) when reactivating a cached route that re-renders from onActivated #15434

Description

@etjson

Vue version

3.5.42 (reproduced on 3.5.42; also present in 3.5.35–3.5.41 per our sessions)

Link to minimal reproduction

https://github.com/etjson/vue-keepalive-repro

A minimal Vue 3.5.42 harness that mirrors our production structure (keep-alive-cached chat view + streaming message list + Teleport + rapid route switches). Important: so far we have been unable to trigger the crash inside this harness (≈7,500 route switches in headless Chrome/Edge + ≈6,000 switches in Electron 44, all without crash). The crash is reliably reproduced in our real Electron app (8+ occurrences today, see "Steps to reproduce" and the production stack traces below). We keep the harness public and will update it as soon as we find a minimal trigger — we are submitting this now because the production evidence is precise and the failure is severe (whole renderer tree corruption → forced page reload).

Steps to reproduce

In a production Electron app (Windows 10 22H2, Chromium 152):

  1. Layout: <router-view> wrapped in <keep-alive :include="['ChatView','OfficeView']">. Route components are imported synchronously (no async components in the kept-alive tree — the 使用keepalive+suspense缓存asyncsetup组件时切换过快就会报错null of parentNode #5993 workaround is already in place).
  2. Open the chat page and send a message. The client optimistically inserts a user bubble and a streaming assistant bubble (temporary negative ids -1, -2), then applies streaming content/tool events which patch the message list multiple times per second.
  3. While streaming is still in progress, navigate to another route (ChatView is deactivated and moved into the keep-alive cache), then quickly navigate back (activate).
  4. The crash fires ~1–2 s after the second activation. Reproduced consistently (8+ times today; timestamps 15:30, 16:00, 16:15, 16:34, 17:00).

Runtime capture at crash time:

CRASH CONTEXT: route=#/chat || activeId=1877 || generating=[1877]
  || storeMsgIds=[-1|-2]                        // optimistic bubbles, ids not yet backfilled
  || keepalive=[activate@t; deactivate@t+3s; activate@t+9s]
  || msgListInDoc=false || domRows=0            // message DOM not even mounted
  || chatPageRoots=1

Notes:

  • The message DOM is not mounted yet (msgListInDoc=false, domRows=0) while the store already holds the two bubbles — the failing patch targets a subtree whose el is already null during the keep-alive move (activate → move from cache container to active container) racing with the async message-load re-render triggered from onActivated.
  • onActivated triggers an async message reload that replaces the messages array and re-renders the list; this is the "async inner render" racing the keep-alive move.

What is expected?

Switching routes while a kept-alive page (or any descendant) is mid-render/mid-stream must not corrupt the component tree. No crash, no forced reload.

What is actually happening?

TypeError: Cannot read properties of null (reading 'parentNode')
    at remove (runtime-dom)
    at patchKeyedChildren    // keyed diff unmount path
    at componentUpdateFn
    at updateComponent
    at processComponent
    at patch
    at patchBlockChildren

immediately followed by:

TypeError: Cannot destructure property 'bum' of '...' as it is null.
  • Flows through app.config.errorHandler as "Unhandled error during execution of scheduler flush" (runtime-core, https://vuejs.org/error-reference/#runtime-15).
  • Component stack at crash: ChatView > KeepAlive > RouterView.
  • After the first error, our self-healing re-render hits the same corrupted nodes and crashes again; the app escalates to a full page reload (visible white flash + loss of in-flight UI state for the user; backend persistence is unaffected).

System Info

System:
    OS: Windows 10 10.0.19045
    CPU: (20) x64 Intel(R) Core(TM) i9-10850K CPU @ 3.60GHz
    Memory: 12.92 GB / 31.88 GB
Binaries:
    Node: 22.20.0
    npm: 10.9.3
    Yarn: 1.22.22
    pnpm: 11.5.2
Browsers:
    Chrome: 152.0.7977.75
    Edge: Chromium (152.0.4191.53)
npmPackages:
    vue: ^3.5.42 => 3.5.42

Renderer: Electron (Chromium 152). Production app bundle is a Vite build (no SSR, no Suspense).

Any additional comments?

Same family as #5993 / #6463 / #8121. We have already applied all known workarounds and the crash persists:

  • Route components are imported synchronously — no async component inside the keep-alive tree (avoids 使用keepalive+suspense缓存asyncsetup组件时切换过快就会报错null of parentNode #5993 wrapper case).
  • No <transition> around <keep-alive> (avoids the 3.5.x emptyPlaceholder issue).
  • All v-for keys are stable (unique ids per message/segment — not array indexes), so the keyed diff is not being confused by id backfilling.
  • Teleport overlays use :disabled instead of v-if unmounting; onDeactivated closes all floating layers.

The crash still occurs 100% of the time in the real app under the sequence above, and the msgListInDoc=false / domRows=0 capture rules out message-key instability: the failing patch targets a detached/null-el subtree during the keep-alive activate move racing with the async re-render scheduled by onActivated.

If useful I can attach the full production electron.log excerpts (8 occurrences, identical stack) and a stripped-down version of the real component tree.

Related: #5993, #6463, #8121, #10517.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions