Rerender Async Component when it is root node of a Sync component before hydration crash the vue runtime.
Vue version
3.2.41
Link to minimal reproduction
https://github.com/mmis1000/vue-ssr-hydration-bug-report
Steps to reproduce
- Create an SSR app structured like this in vdom
<!-- App.vue -->
<Suspense>
<Child>
</Suspense>
<!-- Child.vue -->
<AsyncWrapper :value="context.value" />
<BadSyncComponent />
<!-- AsyncWrapper.vue -->
<Async />
<!-- Async.vue -->
<template>
<div><div/>
</template>
<script setup>
await new Promise(r => r())
</script>
<!-- Sync.vue -->
<template>
<div><div/>
</template>
<script setup>
context.value = 2
</script>
The Sync.vue is a bad component that tries to change the value from context before rendering, which is already tracked by its sibling component.
This would cause the AsyncWrapper to be re-rendered even it is not hydrated yet.
- Start the app and open it in browser
What is expected?
The app reports hydration mismatch but otherwise functions normally.
What is actually happening?
The whole app crashed with following error.
Uncaught (in promise) TypeError: can't access property "shapeFlag", vnode is null
getNextHostNode runtime-core.esm-bundler.js:6229 getNextHostNode runtime-core.esm-bundler.js:6230 componentUpdateFn runtime-core.esm-bundler.js:5699 run reactivity.esm-bundler.js:187 update runtime-core.esm-bundler.js:5729 updateComponent runtime-core.esm-bundler.js:5554 processComponent runtime-core.esm-bundler.js:5487 patch runtime-core.esm-bundler.js:5085 patchKeyedChildren runtime-core.esm-bundler.js:5849 patchChildren runtime-core.esm-bundler.js:5792 processFragment runtime-core.esm-bundler.js:5472 patch runtime-core.esm-bundler.js:5078 componentUpdateFn runtime-core.esm-bundler.js:5695 run reactivity.esm-bundler.js:187 update runtime-core.esm-bundler.js:5729 callWithErrorHandling runtime-core.esm-bundler.js:155 flushJobs runtime-core.esm-bundler.js:388 promise callback*queueFlush runtime-core.esm-bundler.js:280 queueJob runtime-core.esm-bundler.js:274 effect runtime-core.esm-bundler.js:5727 triggerEffect reactivity.esm-bundler.js:396 triggerEffects reactivity.esm-bundler.js:386 trigger reactivity.esm-bundler.js:354 set2 reactivity.esm-bundler.js:525 setup SyncInner.vue:9 callWithErrorHandling runtime-core.esm-bundler.js:155 setupStatefulComponent runtime-core.esm-bundler.js:7204 setupComponent runtime-core.esm-bundler.js:7159 mountComponent runtime-core.esm-bundler.js:5508 hydrateNode runtime-core.esm-bundler.js:4714 hydrateChildren runtime-core.esm-bundler.js:4853 hydrateFragment runtime-core.esm-bundler.js:4879 hydrateNode runtime-core.esm-bundler.js:4694 hydrateSubTree runtime-core.esm-bundler.js:5594 componentUpdateFn runtime-core.esm-bundler.js:5608 run reactivity.esm-bundler.js:187 update runtime-core.esm-bundler.js:5729 setupRenderEffect runtime-core.esm-bundler.js:5743 mountComponent runtime-core.esm-bundler.js:5525 hydrateNode runtime-core.esm-bundler.js:4714 hydrateChildren runtime-core.esm-bundler.js:4853 hydrateElement runtime-core.esm-bundler.js:4815 hydrateNode runtime-core.esm-bundler.js:4705 hydrateSuspense runtime-core.esm-bundler.js:1533 hydrateNode runtime-core.esm-bundler.js:4756 hydrateChildren runtime-core.esm-bundler.js:4853 hydrateElement runtime-core.esm-bundler.js:4815 hydrateNode runtime-core.esm-bundler.js:4705 hydrateSubTree runtime-core.esm-bundler.js:5594 componentUpdateFn runtime-core.esm-bundler.js:5608 run reactivity.esm-bundler.js:187 update runtime-core.esm-bundler.js:5729 setupRenderEffect runtime-core.esm-bundler.js:5743 mountComponent runtime-core.esm-bundler.js:5525 hydrateNode runtime-core.esm-bundler.js:4714 hydrate2 runtime-core.esm-bundler.js:4608 mount runtime-core.esm-bundler.js:4437 mount runtime-dom.esm-bundler.js:1593entry-client.ts:6
System Info
System:
OS: Windows 10 10.0.22621
CPU: (32) x64 AMD Ryzen 9 5950X 16-Core Processor
Memory: 31.10 GB / 63.91 GB
Binaries:
Node: 16.13.2 - C:\Program Files\nodejs\node.EXE
npm: 8.1.2 - C:\Program Files\nodejs\npm.CMD
Browsers:
Edge: Spartan (44.22621.674.0), Chromium (106.0.1370.52)
Internet Explorer: 11.0.22621.1
npmPackages:
vue: ^3.2.31 => 3.2.41
Any additional comments?
The bug is caused by conflict between https://github.com/vuejs/core/blob/aa70188c41fab1a4139748dd7b7c71532d063f3a/packages/runtime-core/src/renderer.ts#L1231 and https://github.com/vuejs/core/blob/aa70188c41fab1a4139748dd7b7c71532d063f3a/packages/runtime-core/src/renderer.ts#L1494
During hydration, the el
exists but subtree
is actually null
.
And when you trigger update in this case. The second line above calls https://github.com/vuejs/core/blob/aa70188c41fab1a4139748dd7b7c71532d063f3a/packages/runtime-core/src/renderer.ts#L2306 to retrieve the subtree for the mount point, which in turns didn't work and crash the whole app.
This bug happened to me when I am using vee-validate in the nuxt3 and it does this
in its setup. The fieldsByPath.value
is a context object shared by its consumer. Which may already has been rendered in the vdom but not hydrated yet like report above.
Related to #6095?
Seems to be exactly the path that cause error.
Although the prompt way of fix this issue seems questionable to me. (I don't have enough knowledge about suspense to say it is right or wrong with confident)
Is it actually actually okay to ignore a partially loaded component when update the tree?