Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v-html on <component> does not work in SSR #12520

Open
danielroe opened this issue Dec 10, 2024 · 20 comments · May be fixed by #12522
Open

v-html on <component> does not work in SSR #12520

danielroe opened this issue Dec 10, 2024 · 20 comments · May be fixed by #12522

Comments

@danielroe
Copy link
Member

Vue version

3.5.13

Link to minimal reproduction

https://play.vuejs.org/#__PROD____SSR__eNp9kcFPgzAUxv+V+i67TIhBLwSXqFniPGzGcWxiEJ7QWVrSFiRZ+N9tC8MdliU9vHy/r33fez3CU9MEXYsQQ2KwbnhmcEUFIUnBOl9MZVoxTezpmGZfHJPwjOeybqRAYUjM9COFhWULCqS7rUzNnZBoo6QoV+nrZk/s2e7SmyScRGcNx6bTq0k4Z4ElGJ1L8c3K4KClsEGPzkrBdWUc1a4xTApNISaeOJZxLn/fvGZUi8uTnleY/1zQD7p3GoV3hRpVhxRmZjJVohnxer/F3tYzrGXRcuu+Aj9QS966jKPtuRWFjX3m82k3dofKMFGmet0bFPo0lAvqnIP3U7C/9XJl9P+4UXDv71Ex2C1+dqjcm3aBUfAQ3EUw/AFR/qMz

Steps to reproduce

<template>
  <div>
    <div>This is visible</div>
    <component :is="'div'" v-html="'<strong>THIS IS NOT!</strong>'" />
  </div>
</template>

What is expected?

The <component> should render in both SSR + client-only

What is actually happening?

THIS IS NOT is not rendered in SSR.

System Info

No response

Any additional comments?

reported in nuxt/nuxt#30202

@edison1105
Copy link
Member

Similar to #6553
see #6553 (comment)

@Gwynerva
Copy link

Gwynerva commented Dec 10, 2024

Well, I really need to use v-html on dynamic component because it can be both div and span depending on what math formula my universial componentt Math is rendering:

<template>
    <component
        :is="isBlockMath ? 'div' : 'span'"
        :class="[$style.math, isBlockMath ? $style.blockMath : $style.inlineMath, meta.freeze && $style.freeze]"
        v-html="mathHtml"
        v-once />
</template>

The workaround is using v-if which sucks and requires duplicating attributes:

<template>
    <div v-if="isBlockMath" v-html="mathHtml" :class="classes" v-once></div>
    <span v-else v-html="mathHtml" :class="classes" v-once></span>
</template>

And if using v-html is not allowed, why it silently eats it up and breaks output html, without showing any errors or warnings?

So there is simply nothing to discuss here, as I see it...
Right now it just don't work as it is intended to work (and it actually does work without SSR), so this is 100% a bug.

@edison1105
Copy link
Member

edison1105 commented Dec 10, 2024

@Gwynerva
Here is a workaround may be better.

@Gwynerva
Copy link

Gwynerva commented Dec 10, 2024

@edison1105 Thank you. Yeah I think this will do. But still as I already said v-html on dynamic component is already working just fine without SSR, so I can't see any reasons why it should not work with SSR.

@edison1105
Copy link
Member

I think using v-html or v-text on a component(or dynamic component) is somewhat of an anti-pattern, not sure if it will be supported, Let's wait for Evan's input.

@Justineo
Copy link
Member

As the use case doesn’t involve custom components, I think it’s legit for dynamic element tags.

@Gwynerva
Copy link

Gwynerva commented Dec 10, 2024

Of course I am talking about "vanilla" HTML tags. Can't think of why someone would need to v-html custom components, which will most likely break them and when you can directly edit these components to accept props, slots and etc.

@Gwynerva
Copy link

Gwynerva commented Dec 11, 2024

Tried to use render function h during setup to conditionally create VNode.
It works without SSR, and partially works in SSR but creates hydration mismatches — on server side it creates an empty comment <!---->. This time not even creating correct wrapping tag, as it was in the example in the first post here.

<script setup>
import { h } from 'vue';

const isBlock = true;
const MyNode = h(isBlock ? 'div' : 'span', { innerHTML: '<strong>Hello World!</strong>' });
</script>

<template>
  <MyNode />
</template>

I am not sure but I think this might be the same bug.

UPDATE:

Turns out when using setup syntax h is not working on server side at all:

<script setup>
import { h } from 'vue';
const MyNode = h('div', { innerHTML: '<strong>Hello World!</strong>' });
</script>

<template>
  <MyNode />
</template>

@Gwynerva
Copy link

To sum up, there are two bugs, both working just fine in normal Vue and not working in Vue SSR:

  1. h is not working in SSR when using <script setup> syntax causing hydration errors. There is a fix from @linzhe141
  2. <component> with dynamic :is and v-html not working in SSR, silently skipping the entire tag body. There is a fix from @noootwo

It was pointed out that second bug needs discussion, but the first one I think obviously is an SSR bug.

What is even worse, the first bug actually prevents a simple possible workaround to avoid using v-html on <component> in the use case I metioned above and therefore, avoid meeting second bug.

@danielroe I think it would be better to rename issue to something like "Dynamic tag generation fails in SSR" to address both problems in one issue.

@yyx990803 Would be really cool to see both or at least the first one to be fixed. Because right now the only way is to duplicate tags directly in <template>, writing all attributes twice, or use sub-components without setup syntax.

@edison1105
Copy link
Member

Tried to use render function h during setup to conditionally create VNode. It works without SSR, and partially works in SSR but creates hydration mismatches — on server side it creates an empty comment <!---->. This time not even creating correct wrapping tag, as it was in the example in the first post here.

<script setup>
import { h } from 'vue';

const isBlock = true;
const MyNode = h(isBlock ? 'div' : 'span', { innerHTML: '<strong>Hello World!</strong>' });
</script>

<template>
  <MyNode />
</template>

I am not sure but I think this might be the same bug.

UPDATE:

Turns out when using setup syntax h is not working on server side at all:

<script setup>
import { h } from 'vue';
const MyNode = h('div', { innerHTML: '<strong>Hello World!</strong>' });
</script>

<template>
  <MyNode />
</template>

This is considered an edge case. IMO, a VNode cannot be rendered directly, at least, it should be <component :is="VNode"/> see, and rendering a VNode directly will no reactivity, such as in this case.

@Gwynerva
Copy link

Gwynerva commented Dec 16, 2024

@edison1105

I'm a relative newcomer to Vue but isn't <component :is="FooComponent" /> supposed to do exactly the same as writing <FooComponent /> but with an ability to replace currently rendering component with something else at runtime with :is attribute?

And if so, what is wrong (besides loosing reactivity) about rendering vnode directly via <VNode /> if rendering it via <component :is="VNode" /> does exactly the same thing?

UPDATE:

I tried rendering h with <component :is="..." /> and it works, resolving my problem with math tag:

<script lang="ts" setup>
// ...

const Tag = h(props.isBlockMath ? 'div' : 'span', { innerHTML: props.html });
</script>

<template>
    <component :is="Tag" :class="classes" v-once />
</template>

Finally, the workaround without creating sub-Vue files.
But the questions remain: if this works with <component ... /> why it is not working without it, considering <component :is="Tag" /> does the same as <Tag />? And also why it is not working with when combining :is="..." and v-html="..." at the same time?

<script lang="ts" setup>
// ...

const Tag = h(props.isBlockMath ? 'div' : 'span', { innerHTML: props.html });
</script>

<template>
    <!-- Works -->
    <component :is="Tag" :class="classes" v-once />
    <!-- Fails -->
    <component :is="isBlockMath ? 'div' : 'span'" :class="classes" v-html="html" v-once />
    <!-- Also Fails -->
    <component :is="isBlockMath ? h('div') : h('span')" :class="classes" v-html="html" v-once />
</template>

@Ghosterbeef
Copy link

@Gwynerva
The first option does not work with SSR, the content containing html markup disappears after hydration.

@Gwynerva
Copy link

Gwynerva commented Dec 20, 2024

@Ghosterbeef It does not work when writing <Tag> directly but works when placing it in <component>.

Still need a comment from @edison1105 or @yyx990803. I think it should work with both <Tag> and <component> syntax because why not?

@Ghosterbeef
Copy link

Ghosterbeef commented Dec 20, 2024

@Gwynerva no its not. If component :is="Tag" is not wrapped in a parent tag and is not its only child, a hydration error occurs

@Gwynerva
Copy link

Gwynerva commented Dec 20, 2024

@Ghosterbeef provide a link to an example in Vue SFC Playground then (there is a "share" button at top right corner). Because I can't reproduce it and everything works fine on my setup.

image

@Ghosterbeef
Copy link

@Gwynerva
here it is

@Gwynerva
Copy link

Gwynerva commented Dec 20, 2024

Yeah now I see. Really weird stuff is happening with SSR...

@edison1105
Copy link
Member

edison1105 commented Dec 23, 2024

@Gwynerva here it is

this should be considered a bug because it works on CSR.
simpler reproduction

if the tag is not a p, it will work as expected.see

Nested <p> tags are automatically "corrected" by the browser. This is because <p> elements cannot contain other block-level elements (including other <p> tags).
image

@Gwynerva
Copy link

Gwynerva commented Dec 23, 2024

So for now this issue covers three problems:

  1. Using <component :is=".." v-html="..." /> in SSR [link]
  2. Using h-created VNodes directly as tags without wrapping them with <component :is=".." /> in SSR [link]
  3. Using h-created VNodes even with <component :is="..." /> also can cause an error [link]

@Gwynerva
Copy link

Gwynerva commented Dec 24, 2024

@Ghosterbeef turns out it's a <p> problem, not a Vue bug!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants