brillout
brillout

Reputation: 7464

[Vue][SSR] Suppress hydration mismatch warning

I'm rendering a Vue component to HTML and it is expected that the DOM rendering/hydration doesn't completely match the HTML rendered version.

How can I supress the hydration mismatch warning?

In React there is https://reactjs.org/docs/dom-elements.html#suppresshydrationwarning (Is there any way to avoid "Text content did not match" warning in SSR with React?) — is there a Vue counterpart to this?

(Context: I'm the author of the new frontend framework Vike and some of my users need that.)

Upvotes: 2

Views: 10431

Answers (5)

codeflorist
codeflorist

Reputation: 543

In Vue 3.5+, it is possible to selectively suppress inevitable hydration mismatches by using the data-allow-mismatch attribute.

Upvotes: 2

Timothy Johns
Timothy Johns

Reputation: 1095

I was getting hydration mismatch errors like this because I was disabling a button and form input elements, and setting some additional classes in various places depending on whether the user had accepted cookies or not.

chunk-YYVLN2RI.js?v=c297baf6:1528 [Vue warn]: Hydration class mismatch on <button class=​"opacity-25 d-block mx-auto btn btn-primary" type=​"submit" disabled>​…​</button>​ 
  - rendered on server: class="opacity-25 d-block mx-auto btn btn-primary"
  - expected on client: class="d-block mx-auto btn btn-primary"
  Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead.
  You should fix the source of the mismatch. 
  at <ERTPNewRoadtrip onUpdate:modelValue=fn<onNewRoadtrip> defaultOriginGpid=undefined defaultDestinationGpid=undefined > 
  at <+Page> 
  at <+Layout> 
  at <RootComponent>

The SSR/SSG html had been rendered as if cookies were not accepted, and whenever anyone who had indeed already accepted cookies loaded the page, they'd get the hydration mismatch. The cookie consent state (ironically) is stored browser-side in a cookie, so was available client-side before initially rendering on the client, but not available at all on the server -- hence the mismatch.

I resolved this by reading the cookies and setting corresponding Vue reactive state in the +onHydrationEnd hook. I believe this allows the DOM to update to the "cookies accepted" state after hydration. I don't notice any flashing and there appears to be no impact to Lighthouse scores.

+onHydrationEnd.ts:

export { onHydrationEnd }
 
import type { OnHydrationEndAsync } from 'vike/types'
import { useCookies } from 'vue3-cookies';
import { useCookieConsent } from '../composables/cookieConsent';

const { cookies } = useCookies();
const { accepted } = useCookieConsent();

const onHydrationEnd: OnHydrationEndAsync = async (
  pageContext
): ReturnType<OnHydrationEndAsync> => {
  accepted.value = cookies.get('cookie_consent_user_accepted') === "true";
}

cookieConsent.ts:

import { ref } from "vue";
import { useCookies } from "vue3-cookies";

const accepted = ref<boolean>(false);
const expireTimes = (60 * 60 * 24 * 180);

export function useCookieConsent() {
  return { accepted };
}

export function acceptCookieConsent() {
  const { cookies } = useCookies();
  cookies.set('cookie_consent_user_accepted', "true", expireTimes);
  accepted.value = true;
}

Example of use of 'accepted' in various Components:

<template>
  <div class="border border-primary border-rounded rounded-3 pt-3">
    <h2 class="text-center mb-3" :class="!accepted ? 'opacity-25' : 'opacity-100'">New Road Trip</h2>
  </div>
</template>

<script setup lang="ts">
import { useCookieConsent } from '../composables/cookieConsent';

const { accepted } = useCookieConsent();
</script>

CookieConsentBanner.vue

<template>
  <div v-if="!accepted" class="fixed-bottom w-100 bg-primary d-flex justify-content-between align-items-center">
    <div class="d-inline-flex mx-3 text-light">
      <div class="d-inline-flex align-items-center">We use cookies.</div>
      <div class="d-inline-flex align-items-center">
        <button class="btn btn-link link-info mx-2" @click.prevent="learnMore()">Learn more.</button>
      </div>
    </div>
    <span>
      <button class="mx-3 btn btn-light my-2 py-0"
        @click.prevent="acceptCookieConsent()">
        Got It
      </button>
    </span>
  </div>


</template>
<script setup lang="ts">
import { 
  useCookieConsent,
  acceptCookieConsent,
} from '../composables/cookieConsent';

const { 
  accepted,
} = useCookieConsent();

function learnMore() {
  // removed for brevity
}
</script>

Upvotes: 0

jtpl artisan
jtpl artisan

Reputation: 1

Vue warn

enter image description here

[Vue warn]: Hydration node mismatch:

  • rendered on server: <main data-v-inspector=​"components/​Header.vue:​2:​5">​…​​
  • expected on client: span at at

Upvotes: -1

zfedoran
zfedoran

Reputation: 3046

I ran into something similar, this is what worked for my use-case. I'd love to know if anyone has a better approach.


If you have a component that you'd like to ignore on the server side, you could use the following approach.

First create a <ClientOnly> component similar to what vitepress has.

import { ref, onMounted, defineComponent } from 'vue'

const ClientOnly = defineComponent({
  setup(_, { slots }) {
    const show = ref(false)
    onMounted(() => {
      show.value = true
    })
    return () => (show.value && slots.default ? slots.default() : null)
  }
})

export {
  ClientOnly,
}

Using this in your template will stop the Hydration warning but it won't stop the server side bundle from including the component.

You can add a v-if using the env value from !import.meta.env.SSR.

Here is a modified template-ssr-vue example from vite. You can view the original here.

<script setup lang="ts">
import { ref } from 'vue'
import { ClientOnly } from './ClientOnly'

defineProps<{ msg: string }>()

const count = ref(0)
const treeShakeServerSide = !import.meta.env.SSR;
</script>

<template>
  <h1>{{ msg }}</h1>

  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
    <p>
      Edit
      <code>components/HelloWorld.vue</code> to test HMR
    </p>
  </div>


  <!-- Client only without Hydration warnings -->

  <ClientOnly>
    <div>This will be rendered on client side only</div>

    <div v-if="treeShakeServerSide">This won't be included in the /dist/server/entry-server.js file.</div>
  </ClientOnly>


  <p>
    Check out
    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
      >create-vue</a
    >, the official Vue + Vite starter
  </p>
  <p>
    Install
    <a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
    in your IDE for a better DX
  </p>
  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>

<style scoped>
.read-the-docs {
  color: #888;
}
</style>

Upvotes: 0

Sasbadi Wan
Sasbadi Wan

Reputation: 79

I wish that I can help by commenting on your post instead of posting answers. But you can give this a try:

There is no direct equivalent to React's react-dom/suppressHydrationWarning in Vue, but you can suppress the warning message in a few ways:

By setting the warning option to false in the Vue component:

<template>
  <div>
    <p v-warning="false">This will not generate a warning</p>
  </div>
</template>

By setting the global Vue.config. warning option to false :

Vue.config.warning = false

By setting the environment variable VUE_WARNING_DISABLE to true :

export VUE_WARNING_DISABLE = true

Or by using the vue-cli-plugin-suppress-warnings plugin.

Upvotes: 0

Related Questions