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

[Vue warn]: Hydration node mismatch: #12266

Closed
kylemod opened this issue Oct 21, 2021 · 23 comments
Closed

[Vue warn]: Hydration node mismatch: #12266

kylemod opened this issue Oct 21, 2021 · 23 comments

Comments

@kylemod
Copy link

kylemod commented Oct 21, 2021

Environment


  • Operating System: Linux
  • Node Version: v14.18.1
  • Nuxt Version: 3.0.0-27243104.5e903ae
  • Package Manager: Yarn
  • Bundler: Vite
  • User Config: meta, buildModules
  • Runtime Modules: -
  • Build Modules: nuxt-windicss@2.0.2

Describe the bug

A warning occurs in console when i add a @click attribute in html tag

[Vue warn]: Hydration node mismatch:
- Client vnode: i 
- Server rendered DOM: <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu">
  <line x1="3" y1="12" x2="21" y2="12"></line>
  <line x1="3" y1="6" x2="21" y2="6"></line>
  <line x1="3" y1="18" x2="21" y2="18"></line>
</svg>  
  at <Navbar> 
  at <AsyncComponentWrapper> 
  at <Default name="default" > 
  at <AsyncComponentWrapper name="default" > 
  at <NuxtLayout key=0 name=undefined > 
  at <RouterView> 
  at <NuxtPage> 
  at <App> 
  at <Root>

Reproduction

.

Additional context

<template>
  ... 
  <a @click="menu"><i data-feather="menu"></i></a>
</template>
<script setup>
  const menu = () => {
    console.log('menu')
  }
</script>

Logs

No response

@wobsoriano
Copy link

wobsoriano commented Oct 21, 2021

Do you still get hydration warnings when you wrap the <template contents in a single root element?

@kylemod
Copy link
Author

kylemod commented Oct 21, 2021

Do you still get hydration warnings when you wrap the <template contents in a single root element?

Yes, here is the full source code

<template>
  <div class="fixed top-0 left-0 w-full z-10 p-3 dark:text-white backdrop-filter backdrop-blur-sm flex flex-wrap">
    <div class="w-1/3">
      <!-- warning here! -->
      <a @click="menu" class="dark:text-white inline-block mt-1"><i data-feather="menu"></i></a>
      <!-- -->
    </div>
    <div class="w-1/3 text-center font-bold">
      <img src="https://nuxtjs.org/design-kit/colored-logo.svg" class="inline h-7 mr-1 -mt-2">
      <p class="inline text-2xl dark:(text-gray-200)">Nuxt<span class="text-green-400">3</span></p>
      <span class="inline ml-2 px-2 py-0.5 rounded bg-dark-300 text-xl">UI</span>
    </div>
    <div class="w-1/3 text-right">
      <a class="dark:text-white inline-block mt-1"><i data-feather="moon"></i></a>
    </div>
  </div>
</template>

<script setup>
  const menu = () => {
    console.log('menu')
  }
</script>

@phoenix-ru
Copy link
Contributor

Quite obvious that you have some transformation going on, as i[data-feather="menu"] gets transformed to an actual svg icon, but the client probably doesn't know about it.
Are you doing the transformations on build time or are you doing them on server render? In case it is on SSR, you should probably disable it on server or move it to build step, so that both client and server are in sync

@kylemod
Copy link
Author

kylemod commented Oct 21, 2021

Quite obvious that you have some transformation going on, as i[data-feather="menu"] gets transformed to an actual svg icon, but the client probably doesn't know about it. Are you doing the transformations on build time or are you doing them on server render? In case it is on SSR, you should probably disable it on server or move it to build step, so that both client and server are in sync

In my case, i just included feather icon on script tag in nuxt.config.js

export default defineNuxtConfig({
  meta: {
    script: [
      { src: 'https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js' },
      { src: 'feather.init.js' }
    ],
  }
})

I tried using <client-only></client-only> so that those part is client only but it seems like nuxt 3 doesn't support <client-only></client-only> and it shows error on my terminal

[Vue warn]: Failed to resolve component: client-only
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.

@alexanderkraus
Copy link

alexanderkraus commented Oct 21, 2021

If you want to use the whole feather-icons package, I think it's easiest to implement a component which renders the icon to SVG, like so:

<script>
import feather from "feather-icons";

export default {
  props: {
    icon: String,
    validator: function (value) {
      return typeof feather.icons[value] !== "undefined";
    },
  },
  render(h) {
    let svg = "";
    if (typeof feather.icons[this.icon] !== "undefined") {
      svg = feather.icons[this.icon].toSvg();
    }
    return h("i", {
      class: {
        icon: true,
      },
      domProps: {
        innerHTML: svg,
      },
    });
  },
};
</script>

<style scoped>
i {
  display: inline-block;
}
</style>

which could be used as follows

<template>
  <div id="app">
    <Feather-Icon icon="x" />
    <Feather-Icon icon="y" />
    <Feather-Icon icon="award" />
    <Feather-Icon icon="briefcase" />
  </div>
</template>

<script>
import FeatherIcon from "./components/FeatherIcon";

export default {
  name: "App",
  components: {
    FeatherIcon,
  },
};
</script>

<style>
.icon {
  color: #364fc7;
}
</style>

see https://codesandbox.io/s/happy-kare-hoy65?file=/src/components/FeatherIcon.vue:0-536

There are also other packages like vue-feather, but IMHO those packages often provide features which are not always required in every project...

@danielroe
Copy link
Member

tracking need for <client-only> at #11884

@henningko
Copy link
Sponsor

Trying to wrap my head around this..:

[Vue warn]: Hydration node mismatch:
- Client vnode: div 
- Server rendered DOM: 
<form class="row flex flex-center"> <empty string> 
  at <Index onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< undefined > > 
  at <BaseTransition mode="out-in" appear=false persisted=false  ... > 
  at <Transition name="page" mode="out-in" > 
  at <RouterView > 
  at <NuxtPage> 
  at <App> 
  at <NuxtRoot>

Which probably stems from a problem with my if/else, as the form referenced up top is the form shown if isAuthenticated() fails:

<template>
  <div class="w-full min-h-screen bg-white dark:bg-black dark:text-white p-8">
    <div v-if="isAuthenticated()">
      <Logout />
      <h1 class="text-7xl font-bold mb-8">Brief.</h1>
      <Content />
    </div>
    <Auth v-else />
  </div>
</template>
<script setup lang="ts">
const { $supabase } = useNuxtApp();
const loading = ref(false);
const { isAuthenticated } = await useAuth();
</script>

useAuth.ts

import type { Session } from "@supabase/supabase-js";

// const authenticated = ref<boolean>(false);

// session is null when not logged in
const userSession = ref<Session | null>(null);
const userCookieSet = ref<boolean | null>(null);

export default async () => {
  const isAuthenticated = () => {
    return userSession.value?.user && userCookieSet.value;
  };

  return {
    userSession,
    userCookieSet,
    isAuthenticated,
  };
};

whereby userSession is set by supabase.auth, and a subsequent request to /api/auth ensures that a cookie is being set so that Nuxt's API can make requests to supabase on behalf of the user. Full repo: https://github.com/henningko/briefxyz/tree/api
New to Nuxt3/SSR/Supabase and biting off more than I can chew, probably, so please don't hold back with criticism :-)

Environment

  • Operating System: macOS
  • Node Version: v17.4.0
  • Nuxt Version: 3.0.0-27398533.8edd481
  • Package Manager: Yarn
  • Bundler: Vite
  • User Config: buildModules, RuntimeConfig
  • Runtime Modules: -
  • Build Modules: nuxt-windicss@2.2.2

@paddotk
Copy link

paddotk commented Nov 22, 2022

I had this problem with font-awesome icons. My workaround was this:

<font-awesome-icon
    v-if="!loading"
    icon="fa-solid fa-phone"
/>
<script lang="ts" setup>
const loading = ref(true);

const nuxtApp = useNuxtApp();
nuxtApp.hook("page:finish", () => {
  loading.value = false;
});
</script>

This waits for the page to finish, i.e. the content to be present client-side.

@ReaganM02
Copy link

ReaganM02 commented Jan 6, 2023

I had the same issue. I tried wrapping the div with ClientOnly and it works. I don't see the warning in the console log anymore.

<ClientOnly>
	<div v-if="auth.user">
		<div class="font-bold text-sm">{{ auth.user.name }}</div>
		<div class="text-xs text-slate-400 font-light">{{ auth.user.email }}</div>
	</div>
 </ClientOnly>

@letzfets
Copy link

I have a very similar issue and it makes sense to me:
I'm displaying the current screen size of vuetify3 in the DOM. Server side certainly does not know about the size of the screen of the client, so server-side the variable is set to md, while client ist xs: That leads to a hydration warning:

[Vue warn]: Hydration text mismatch:
- Client: "xs"
- Server: "md"

and terribly clutters my console. How can I turn these warnings off?

Copy link
Member

You should pay attention to the warnings and add some handling on the client side to avoid updating the variable until your app has mounted. You can use onNuxtReady composable in the next Nuxt release to do so: nuxt/framework#9478.

@letzfets
Copy link

You should pay attention to the warnings and add some handling on the client side to avoid updating the variable until your app has mounted. You can use onNuxtReady composable in the next Nuxt release to do so: nuxt/framework#9478.

I try to do my best taking all warnings and errors serious. Not sure how I could avoid it in my example, though: the server does not know anything about the screen size of the client. So the pages are served with my variable being the default value md, but the client might have any other screen size: xs, sm, lg, or xl.

@danielroe danielroe added the 3.x label Jan 19, 2023
@danielroe danielroe transferred this issue from nuxt/framework Jan 19, 2023
@Maximus1000
Copy link

Maximus1000 commented Feb 1, 2023

I got this error by using conditional logic inside a named slot in a base container template, like this:

<BaseContainer.vue>
  <slot name="loading">
      <template v-if="loading">
        loading...
        <!--<BaseLoadingFeather />-->
      </template>
    </slot>
    ...other slots
 </BaseContainer>

Note how I thought like others that it might have been the feather library (loading in a separate component) that was the issue. Negative, removing that library had no effect. The issue was having the v-if="loading" inside the named slot. Upon removing the conditional logic from the Base template and moving that up to the parent template, no hydration error. Note in the above, I'm receiving loading as a prop. I may have been wrong here in the first place regarding conditional rendering inside a slot but still thought this was worth sharing in case someone else encounters the same. No v-if / conditional rendering inside named slots (or at least not in this example).

Edit: More testing, identified that I can add conditional logic on BaseLoadingFeather component that loads the feather library without use of onNuxtReady if the loading slot is wrapped in a ClientOnly component. Thought that I had tested that specific (seems no), passing without hydration issues like this inside a base component template. Sharing in case this helps someone.

// ShowRecordContainer
<ClientOnly>
  <slot name="loading">
    <BaseLoadingFeather v-if="loading" />
  </slot>
</ClientOnly>

Use case is like this:

//student/[slug].vue
 <ShowRecordContainer
    :loading="loading"
    @close="emit('close')"
  >
  <template #header>....
  <template #default>....
  // nice, the loader works in all show record pages of various data types without issue.

@Livijn
Copy link

Livijn commented Apr 13, 2023

I'm using <client-only> as well but that results in flickering on load. Also using <font-awesome-icon>.

@snimavat
Copy link

I had this problem with font-awesome icons. My workaround was this:

<font-awesome-icon
    v-if="!loading"
    icon="fa-solid fa-phone"
/>
<script lang="ts" setup>
const loading = ref(true);

const nuxtApp = useNuxtApp();
nuxtApp.hook("page:finish", () => {
 loading.value = false;
});
</script>

This waits for the page to finish, i.e. the content to be present client-side.

This fixed the issue for me
I had <Icon name="fa-brands:github" class="w-5 h-5" /> which was causing the issue

I am new to nuxt, can you confirm tht this works with SSR !

@oleksandr-dukhovnyy
Copy link

@henningko

This is a fairly common hydration mistake.
Making these mistakes comes from a misunderstanding of how SSR and hydration actually work.

So, I'll try to explain briefly.

Your code:

<template>
  <div class=".1">
    <div v-if="isAuthenticated()">
      <logout />
      <h1 class=".2">.3</h1>
      <Content />
    </div>
    <div class="auth" v-else>.4</div>
  </div>
</template>

<script setup lang="ts">
  const { $supabase } = useNuxtApp();
  const loading = ref(false);
  const { isAuthenticated } = await useAuth();
</script>

When Nuxt does an SSR for this page, it generates an HTML structure:

<div class=".1">
  <div class="auth">.4</div>
</div>

Because isAuthenticated will always be false because the work happens on the server, and there are no cookies on the server, no tokens, nothing to do with authentication.

Next, Nuxt will pass this code to the client as HTML and JS.

But the trick is that the HTML structure is now "dead". That is, it is not connected in any way with the state, there are no listeners on it.
It's just non-interactive HTML. The process of "revitalizing" the HTML structure is called "hydration".

When hydrated, Vue doesn't re-render the HTML, but rather "picks up".

The pickup goes like this: Vue has a virtual component tree (virtual DOM) and just recursively goes through it, connecting the real DOM node to the virtual DOM node along the way.

The v-if directive doesn't just add style="display: none" (as the v-show directive does), but completely cuts the node physically from the DOM.

Also, the setup code is executed BEFORE hydration or rendering. That is, the authorized client receives true in v-if, unlike the server.

So if the client is authorized, when Vue tries to hydrate your component, it will try to synchronize the next virtual DOM:

<div class=".1">
   <div>
     <logout />
     <h1 class=".2">.3</h1>
     <Content />
   </div>
</div>

With the actual (which came from the server):

<div class=".1">
   <div class="auth">.4</div>
</div>

Which will cause a sync error.


As a quick fix, consider using v-show instead of v-if. But this solution is bad for several reasons. For example, there is no v-show-else. Secondly, style="display: none" will solve the hydration problem, but if you have a lot of elements that depend on authorization, then using v-show will significantly increase both the number of DOM nodes, which will slow down the page, and the virtual size DOM, each node of which is served by reactivity.

As the right solution, it is worth considering the migration of authorization to the client in full:

<template>
   <div class=".1">
     <div v-if="isAuthenticated">
       <logout />
       <h1 class=".2">.3</h1>
       <Content />
     </div>
     <div class="auth" v-else>.4</div>
   </div>
</template>

<script setup lang="ts">
   import { ref } from 'vue';

   const { $supabase } = useNuxtApp();
   const loading = ref(false);
   const { isAuthenticated: checkIsAuthenticated } = await useAuth();
   const isAuthenticated = ref(false);

   onMounted(() => {
     isAuthenticated.value = checkIsAuthenticated();
   });
</script>

The onMounted hook only runs on the client.

Even better, wrap your component in a custom <ClientOnly></ClientOnly> tag.

Hope it's a little clearer.)

@Lrodriguez2027
Copy link

I had the same issue. I tried wrapping the div with ClientOnly and it works. I don't see the warning in the console log anymore.

<ClientOnly>
	<div v-if="auth.user">
		<div class="font-bold text-sm">{{ auth.user.name }}</div>
		<div class="text-xs text-slate-400 font-light">{{ auth.user.email }}</div>
	</div>
 </ClientOnly>

This works for FontAwesome Items in Nuxt 3 , Thanks for sharing

@yjm020811
Copy link

Will there still be SEO effects if clientOnly is used?

Copy link
Member

If you are using <ClientOnly>, then these components and their HTML will not be rendered on the server. So, yes.

@oleksandr-dukhovnyy
Copy link

Will there still be SEO effects if clientOnly is used?

In general, the question is rather complex.

On the one hand, if you answer the question "will there be a disaster for SEO if I use Client Side Rendering?", then I think that most likely not.
Hasn't Google learned how to index Client Side Rendering in 10 years? Sounds unbelievable.
It is worth noting, however, that I did not find a single study on this topic.
You can run two sites (SSR and CSR) with almost the same content and look at the SEO metrics.

On the other hand, SSR greatly affects SEO metrics.
For example, on the speed of displaying content.

So the answer is something like this: SSR is very likely to improve SEO performance, but even without SSR, you will not have a disaster.

@sanjarbarakayev

This comment was marked as resolved.

@oleksandr-dukhovnyy
Copy link

@sanjarbarakayev,

Let's simplify the context to focus on the problem.
Let's look at the following problematic component pages/page.vue:

/app.vue

<template>
  <NuxtPage />
</template>

pages/page.vue

<template>
  <div>
    <p>Data...</p>
    <div v-if="loading">loading</div>
    <div v-else>not loading</div>
  </div>
</template>

<script lang="ts" setup>
  // Some random value, like loading, etc
  const someComputedValue = ref(Math.random() > 0.5);

  const loading = computed(() => someComputedValue.value);
</script>

When reloading the page, sometimes (with a ~50% chance) a hydration error will occur.
About the same as yours.

You need to synchronize the loading value on the server and client.

So, there are three ways to implement this synchronization, depending on your context:

  • ClientOnly
  • onMounted
  • load only on client

ClientOnly

Disable SSR altogether for the problem area.

pages/page.vue

<template>
  <div>
    <p>Data...</p>

    <ClientOnly>
      <div v-if="loading">loading</div>
      <div v-else>not loading</div>

      <template #fallback>
        <Loading />
      </template>
    </ClientOnly>
  </div>
</template>

<script lang="ts" setup>
  // Some random value, like loading, etc
  const someComputedValue = ref(Math.random() > 0.5);

  const loading = computed(() => someComputedValue.value);
</script>

onMounted

pages/page.vue

<template>
  <div>
    <p>Data...</p>

    <div v-if="loading">loading</div>
    <div v-else>not loading</div>
  </div>
</template>

<script lang="ts" setup>
  // Some random value, like loading, etc
  const someComputedValue = ref(Math.random() > 0.5);

  const clientEnv = ref(false);
  onMounted(() => (clientEnv.value = true));

  const loading = computed(() => {
    if (!clientEnv.value) {
      return true;
    }

    return someComputedValue.value;
  });
</script>

You might figure out that the code is executed differently on the client, but in general the method looks like this.
Essentially the same as ClientOnly, but your <div>loading</div> will be rendered on the server.
Do you need your <div>loading</div> in SEO? This is a big question.

load only on client

In this case, you ensure that notificationStore.loading on the server is always the same as in setup on the client. This is achieved, for example, this way:

/app.vue

<template>
  <NuxtPage />
</template>

<script setup>
  import useNotificationStore from '~/stores/notification';

  const notificationStore = useNotificationStore();
  onMounted(() => notificationStore.loadNotifications());
</script>

Now throughout the entire project you won't have hydration problems due to notificationStore.loading.


References:


Which to choose?

Without knowing your context, I suspect that notificationStore is needed to display client notifications.
In this case, rendering loaders will be useless for SEO, and the SEO bot will always be without an account, i.e. it will see something like: 'You have no notifications.'

In this case, the option with ClientOnly is suitable for you.

If you need SEO to display notifications, then I recommend the load only on client option.

@Ramdeep-zentral
Copy link

import * as flowbite from 'flowbite';

export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component("flowbite", flowbite);
});

Working perfectly

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

No branches or pull requests