#15468·Vue.js

Node.normalize() removes Fragment text anchors and causes subsequent updates to fail

Author: eric-gitta-mooreCreated Sep 9, 2026Updated Sep 10, 2026
Labelshas workaround🔩 p2-edge-case

Vue version

3.5.42

Link to minimal reproduction

https://play.vuejs.org/#eNp9U99P2zAQ/lcOP0FFnI2yPaBSbbAibdJggmpPlqaQXFODY0f+Ucqq/u87O01pJ+Apzvfdfffd+bxiX9uWLwKyMzZypZWtB4c+tGOhZdMa62EFFmewhpk1DQhGsYIJnQ8GcGm0t0Y58HOE0uhKeml0oeDKFnWD2nMY5EIT40h1bp7gPGodehvwqJOYNEEVHh0UJCJtlbWF9c+wcaKNbQol/0pdpxoWdYUWK/h287PTngVdxqLbULww1fPhEbmuTBmSiXtC+JYnbi30KO9KUJv047Fpow36Axg1hdTpROeDLHtp02KkoDFBe6w4ZFkfdh+8JxNfSiXLx3PB9twINv7I4bqHIPoZ5V3KWwKbaR3EL+WfcJiaulaUbAtdzv9Pjzanc+k2NARHI+1vASJkrNs13HcMi0zONvWoTscS79pCj6+kdZ4mFc/7zB3G696jRvneFAmo5ILkUTkc3yIxJSY3fQdEd/PONwPfUWDHzDsqMZM1f3BG03auYqxgpWlaqdDetPHanWBnkJjIFUqZpx8Jiyt23OPlHMvHV/AHt4yYYL8sOrQLWuwt5wtbo+/oyd01Lum8JRtTBUXR75C36IwK0WMXdhF0RbZ34pLb7+mN0X5P3WTpUbu+qWg0Rq5TfHp2l++0/mJ3yE9TntBrmuKfBdqoSQMc8k/89CRi3v3eop/5Bz5k639nn1Ys

Steps to reproduce

  1. Open the reproduction link (Vue 3.5.42, DEV mode, SSR off).
  2. Click "1. Normalize body" to execute document.body.normalize() inside the preview frame.
  3. Click "2. Toggle branch".
  4. Observe the nextSibling error: First/Second remain visible instead of Replacement branch.

Verified control: without calling normalize(), Toggle branch switches to Replacement branch and back successfully.

What is expected?

Node.normalize() removes empty text nodes and merges adjacent text nodes. Vue Fragment boundaries use empty text nodes, so normalizing an ancestor may remove these boundaries while the VNodes retain references to the original nodes.

Would it be possible to preserve compatibility between Node.normalize() and Fragment anchors?

What is actually happening?

The issue is reproducible. After clicking Normalize body and then Toggle branch, the update fails: First/Second remain visible instead of Replacement branch. Without normalization, switching in both directions works.

Error stack:

TypeError: Cannot read properties of null (reading 'nextSibling')
    at nextSibling (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:7607:30)
    at removeFragment (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:6128:11)
    at remove (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:6107:9)
    at unmount (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:6091:18)
    at patch (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:5503:4)
    at patchBlockChildren (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:5696:4)
    at patchElement (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:5662:4)
    at processElement (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:5584:5)
    at patch (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:5525:32)
    at ReactiveEffect.componentUpdateFn [as fn] (http://127.0.0.1:5179/node_modules/.vite/deps/vue_dist_vue__esm-bundler__js.js?v=895c4d76:5852:5)

System Info

System:
  OS: macOS 15.5
  CPU: (12) arm64 Apple M3 Pro
  Memory: 326.52 MB / 36.00 GB
  Shell: 5.9 - /bin/zsh
Binaries:
  Node: 22.22.2 - /Users/<redacted>/.asdf/installs/nodejs/22.22.2/bin/node
  Yarn: 1.22.22 - /Users/<redacted>/.asdf/installs/nodejs/22.22.2/bin/yarn
  npm: 10.9.7 - /Users/<redacted>/.asdf/plugins/nodejs/shims/npm
  pnpm: 10.33.2 - /Users/<redacted>/.asdf/installs/nodejs/22.22.2/bin/pnpm
  bun: 1.3.13 - /Users/<redacted>/.asdf/shims/bun
  Deno: 2.7.9 - /opt/homebrew/bin/deno
npmPackages:
  vue: 3.5.42 => 3.5.42

Any additional comments?

Related reports:

A simplified workaround skips normalize() calls on the application root and its ancestors:

const originalNormalize = Node.prototype.normalize;

Node.prototype.normalize = function () {
  const appRoot = document.getElementById('app');
  if (appRoot && this.contains(appRoot)) return;
  return originalNormalize.call(this);
};