hoisted element attributes not being updated on re-render from a failed hydration
Vue version
3.2.37
Link to minimal reproduction
Steps to reproduce
Run the example with SRR on and see, that the console spits out a different id than is rendered.
It even gives you an error message telling you that there is a missmatch
What is expected?
I would expect it to hydrate properly
What is actually happening?
The const id is never changed in the template
System Info
System:
OS: Windows 10 10.0.19044
CPU: (8) x64 Intel(R) Core(TM) i7-4702MQ CPU @ 2.20GHz
Memory: 5.95 GB / 15.88 GB
Binaries:
Node: 17.1.0 - D:\Programme\nodejs\node.EXE
Yarn: 1.22.15 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
npm: 8.13.2 - D:\Programme\nodejs\npm.CMD
Browsers:
Chrome: 103.0.5060.114
Edge: Spartan (44.19041.1266.0), Chromium (103.0.1264.49)
Internet Explorer: 11.0.19041.1566
Any additional comments?
I run into this bug because I created a unique id in my component setup that is never gonna change for the life of the component. Thats why I defined it as const an not as a ref. Then I used the component in vitepress and it stopped working.
When I use a ref instead of direct assignment, it works as expected
What you are asking for is technically impossible. The value of a const cant be re-assigned (that's what makes it a const), yet this is what needs to happen to replace the random value generated on the client during setup
with the one generated on the server.
When I use a ref instead of direct assignment, it works as expected
What setup are you using for SSR?
A ref on its own does not make this work. You can change the implementation in your playground to use a ref and you still get a hydration error.
This is because during the SSR phase, this piece of state has to be some how collected during render, sent to the client as part of a JSON String representing the current app state, and then be re-applied to the component during hydration. That is what the SSR architecture of the app needs to handle, it's not something Vue itself can provide on its own.
Conceptually this little Id is just like other pieces of request-specific state that your app has. For example, you fetch a list of posts on the server and render them. to successfully hydrate this data on the client, the server app has to extract that data from the component, send it as JSON in the HTML to the client, re-apply it to the app and then hydrate the app.
Usually, for state like a list of posts, SSR frameworks use state managers like vuex or pinia. Nuxt 3 for example, also offers a special useState()
composable that can be used instead of a plain ref to generate local state in a component that can be hydrated. The linked nuxt docs page has an example involving a randomly generated number that pretty much mirrors your example.
@LinusBorg Yes you still get the hydration error but the UI updates accordingly to the correct value once the component was hydrated. I tested it in the sfc playground and you can see, that the value logged in the console is the same as the one which is being shown. And that's totally fine for my usecase (and i consider this correct hydration)
Wow, this took me a second. Mostly because I could still not see the problem in the playground with or without refs - each time, the dom matched the client-side generated id.
That is, until I thought about what you might actually be doing with that id in you app: use it as an id
attribute. After I did that in the playground, the problem became appearant:
it did update the interpolation, but it did not update the id attribute? Why does that happen? Because the Vue compiler is "too" smart here: it realizes that the id attribute is set from a const, and thus can never change. So it defines this attribute as static, meaning re-renders will skip updating it to improve update performance.
But in this case, we want to have it updated as we need to forcibly change it once, from the original SSR value to the new, then from hereon out constant value, as we failed to hydrate it.
I'll leave it to Evan or someone else more experienced in the hydration implementation to decide how this should be handled and wether this is an actual bug or more of a caveat in SSR hydration of non-static state. Maybe when bailing on hydration we need to somehow skip these kinds of optimizations?
Tip: use a custom Directive to assign such a random attribute. this keeps it out of the server-generated HTML, and you don't get any hydration errors (which are negative for you intial render performance and give your ugly warnings during dev).
Something like this is enough: