core icon indicating copy to clipboard operation
core copied to clipboard

`<component>` of void element with content causes hydration mismatch

Open Ingramz opened this issue 1 year ago • 2 comments

Vue version

3.5.11

Link to minimal reproduction

https://play.vuejs.org/#__SSR__eNqFVNtu00AQ/ZXBLwEptUnKTVZSCapKwEOpAPGCK+TYE2fLene1u3ZTRf53ZnxJ3CgKebAyZy4+M3PGu+CjMWFdYRAHC5dZYTw49JW5SpQojbYerrXyqPytzhHWVpcwCaMRxsmTRCUq08p5oCd7YAm/EwWw4weAfzIYwwQlluScTDtUpSWjZrBT761YVR5dDLumB/uCcVePf33NUV2P233RFiab4M8opYZHbWX+gih2vqHuqTpH/EYcRVmM0WdMwdmMQjbeGxdHUVqnPrUuLITfVKvKoe1bCDNdRlX0bjb/MHv9fjKFVDLLXxWGD24yIjZu+n6PDv4OYes+UYuoWxqtiwyPpZGpR7IAFrmoIZOpc8sk6AsmQesi53ir9cVaWwpSbAg1vD0JIGao95AZdYUjqkz/FtHofcE08I4S16KgbrQiPbXz5VeXRki034wXJJEkoJF1JJIgpQU9fm0xbyvsO6ScDWZ/T+APbstYEtxZpMHWRGrvo6EXSKTZffPjliQwcpY6ryRFn3F+R6dlxRy7sE+Vyon2KK5l+6W9CqGKn+5mS2NyQ1NMdNgMR9NdXJ9p/UD3MnzT5iWqoSkendbxXYJMVUEb8VSNxp7jWii8s9q4xQ54TTGk6gmaq5evzquD16IVn2osWCGcG7Laac/1hVgPEJ8GLJfLw3W0ASuh8iHkcA3/19ecBdZmnVbZfCSzPcXOHvhTPZQOT3JsvwTEYtcNI2QbmuaZVI91+6dGy1ukUV+Gb8PZLGj+AeRfpSE=

Steps to reproduce

Reproduction contains an example where a component called ContentNode references itself while rendering a tree of content elements. When enabling SSR, hydration mismatches are reported, but when comparing the generated HTML, everything seems OK.

What is expected?

No hydration mismatches.

What is actually happening?

Hydration mismatches are reported.

System Info

No response

Any additional comments?

No response

Ingramz avatar Oct 09 '24 15:10 Ingramz

I'm not sure if this counts as a bug, but the issue is that the vnode of img contains children, which is unreasonable because the img tag is self-closing and cannot have children. Maybe we should ignore the children of void tags when creating vnode.

You shouldn't add child nodes to the img tag, as it is unreasonable. You can modify it like this:

<template>
  <component :is="node.name" v-if="node.type === 'element' && node.name !== 'img'" v-bind="node.attributes">
    <ContentNode v-for="node2 in node.content" :node="node2" />
  </component>
  <template v-else-if="node.type === 'text'">{{ node.text }}</template>
  <img v-else-if="node.name === 'img'" v-bind="node.attributes" />
</template>

Note: this will still report a mismatch in Playground because there is a bug in Playground, but it works correctly when the project is started locally.

edison1105 avatar Oct 10 '24 03:10 edison1105

Thank you for investigating, I can confirm that you are correct that creating a dedicated else-if branch for void elements with no content easily works around this. Playground reporting hydration mismatches despite not using a void element was driving me insane. 🤦

Also now that you explained that the img vnode still contains children, then if I understand correctly, when comparing to the rendered HTML, which does not contain any children, it triggers the mismatch.

As for how to better handle this situation, I have no strong preference, but if the children on a void element vnode do not contribute to rendered HTML / DOM, they should be treated as valid and matching. If something visible was rendered, then that should still continue be treated as hydration mismatch due to the resulting HTML being invalid.

Ingramz avatar Oct 10 '24 04:10 Ingramz