Subscribe on changes!

hoisted element attributes not being updated on re-render from a failed hydration

avatar
Jul 10th 2022

Vue version

3.2.37

Link to minimal reproduction

https://sfc.vuejs.org/#__SSR__eNp9jz1uwzAMha8iaIkNxNJQIIPhGugBOnXV4lq0o8D6AUW7g6G7V4o7BA2QjXzk+x65848QxLYCb3lHYMMyEPTKMdZps/X7bnRKnSy1cp182MhtHNEEYhFoDXePscEjsR1hSmxCb9kpo09lNHoXiRnN3tnnQFeBg9PeVrUg/0Vo3Fy9XWoRFzNC1VxqVgIOl19ALH6ujK7LCUdoz8/8SGvsEMQtepc/2ItH/Q2i4i27K0XLd5Re8StRiK2UcRrL37coPM4yVwJXR8aCgGibb/Q/ETCDFT8/MGQWN8AGwWlAwFfMf6tP3IJNyiWefgHbr4cq

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

avatar
Jul 10th 2022

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.

avatar
Jul 10th 2022

@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)

avatar
Jul 10th 2022

I'll reopen this to take another look

avatar
Jul 10th 2022

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:

Playground

Bildschirmfoto 2022-07-10 um 13 16 21

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.

Bildschirmfoto 2022-07-10 um 13 17 54

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?