vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.29k stars 8.27k forks source link

Fatal SSR Hydration crash #7086

Open wucdbm opened 1 year ago

wucdbm commented 1 year ago

Vue version

3.2.44

Link to minimal reproduction

https://github.com/wucdbm/vue-hydration-bug

Steps to reproduce

yarn install
yarn codegen

Working fine

yarn dev

Visit http://127.0.0.1:3000/characters#characterId=9 Popup should be open

Not Working (dev ssr)

yarn ssr

Visit http://127.0.0.1:3000/characters#characterId=9 Popup is not open and console is full of errors

Not Working #2 (pkg build)

yarn build
yarn build:ssr
./dist/vue-test-bug

Visit http://127.0.0.1:3000/characters#characterId=9 Popup is not open and console is full of errors

What is expected?

It is expected that since we serialize the state via URQL's SSR middleware, and since the "query" param (yes, we do pass it via the URL's hash, for SEO reasons) is present, once it's open in the browser, it should quickly update its state and open itself asap, start "loading" the content (should finish ASAP due to URQL SSR cache) and present itself.

I also expect a hydration error since the server doesn't see the hash, that's OK. We will likely go around that with something like ClientOnly or some sort of lazy loading on first browser idle.

I do NOT expect however for the app to fatally crash and the user not having any way to escape since navigation or any interaction with it whatsoever no longer works.

What is actually happening?

Instead, there are a bunch of hydration-related issues which is probably fine (we could use lazy-loading to circumvent that and load the popup on browser idle), but the problem is a runtime-core.esm-bundler.js:40 [Vue warn]: Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core after which nothing works at all and any following error is probably caused by this previous error that caused it to crash somehow.

System Info

System:
    OS: Linux 5.15 Manjaro Linux
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i9-12900H
    Memory: 54.11 GB / 62.47 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 19.0.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 8.19.2 - /usr/local/bin/npm
  Browsers:
    Firefox: 105.0.3
  npmPackages:
    vue: 3.2.44 => 3.2.44

Any additional comments?

I didn't try using a render function to implement the generic components such as popup+card+slots (one of which is conditional) What I did notice though is that if we don't pass any props to the CharacterModal, it doesn't seem to want to check if it's rendered correctly (probably an optimization), hence no breakages, just displays the content coming from SSR - empty card modal in a hidden state.

Besides that, we do use a plugin of our own to build the project and run a dev ssr via vite, but I don't think it's causing any issues.

wucdbm commented 1 year ago

@yyx990803 Managed to narrow this down to instances where a <template> has a v-if on it, in runtime-dom.esm-bundler.js nodeOps.insert parent is NULL

The reason for using a is to avoid polluting the DOM with redundant DIVs

It seems that it can recover successfully from DIVs with v-ifs with SSR content mismatches, but not when it's a template. The template contains multiple children, sometimes something at the start that is always displayed and another chain of v-ifs

When I replace the template with div, but otherwise keep everything else as-is, it won't crash on the first template, but instead keep going and list all the mismatches and re-render. With a template, it would crash hard on the first occurrence and ask me to file a bug report.

Could it be that this is an overlooked case, where because the template does not have its own representation in DOM, it tries to attach whatever should be there to the template which doesn't really exist in DOM and thus crashing hard? Is it a sensible solution in such cases to replace the parent argument passed with the template tag's parent, since the template itself won't render an element in DOM?!

Furthermore, sometimes it makse sense to conditionally pass a slot to a generic component - such as a visual "Card" - sometimes it could have a footer, other times not, depending on state, in which case I'd do <template #footer v-if="something">... content ... and in Card.vue conditionally render a footer container with the slot inside of that if there's any slot content passed in the first place - I am guessing that's what's causing the example repo to crash as described

Today's debugging I was debugging a similar crash some place else, where I was using a template tag to just group a bunch of elements/components to render under the same condition in one of the branches of a v-if-else-if-etc chain, but the root cause seems to be the same problem, as described.

Sadly, that's as far as my understanding of Vue internals goes, I hope this helps!

wucdbm commented 1 year ago

I mean, I just discovered another case where after a rendering mismatch, it crashes hard and it's shit out of luck

I think this needs attention