nuxt: [Vue warn]: Hydration node mismatch:
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
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 23 (4 by maintainers)
@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:
When
Nuxt
does anSSR
for this page, it generates anHTML
structure:Because
isAuthenticated
will always befalse
because the work happens on the server, and there are nocookies
on the server, no tokens, nothing to do with authentication.Next,
Nuxt
will pass this code to the client asHTML
andJS
.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-interactiveHTML
. The process of “revitalizing” theHTML
structure is called “hydration”.When hydrated,
Vue
doesn’t re-render theHTML
, but rather “picks up”.The pickup goes like this:
Vue
has avirtual component tree (virtual DOM)
and just recursively goes through it, connecting the realDOM
node to thevirtual DOM
node along the way.The
v-if
directive doesn’t just addstyle="display: none"
(as thev-show
directive does), but completely cuts the node physically from theDOM
.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 nextvirtual DOM
:With the actual (which came from the server):
Which will cause a sync error.
As a quick fix, consider using
v-show
instead ofv-if
. But this solution is bad for several reasons. For example, there is nov-show-else
. Secondly,style="display: none"
will solve the hydration problem, but if you have a lot of elements that depend on authorization, then usingv-show
will significantly increase both the number ofDOM
nodes, which will slow down the page, and the virtual sizeDOM
, 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:
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.)
tracking need for
<client-only>
at https://github.com/nuxt/nuxt.js/issues/11884Trying to wrap my head around this…:
Which probably stems from a problem with my if/else, as the form referenced up top is the form shown if
isAuthenticated()
fails:useAuth.ts
whereby
userSession
is set bysupabase.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
macOS
v17.4.0
3.0.0-27398533.8edd481
Yarn
Vite
buildModules
,RuntimeConfig
-
nuxt-windicss@2.2.2
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.I had this problem with font-awesome icons. My workaround was this:
This waits for the page to finish, i.e. the content to be present client-side.
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:
which could be used as follows
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…
@sanjarbarakayev,
Let’s simplify the context to focus on the problem. Let’s look at the following problematic component
pages/page.vue
:/app.vue
pages/page.vue
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
Disable SSR altogether for the problem area.
pages/page.vue
onMounted
pages/page.vue
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
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.This works for FontAwesome Items in Nuxt 3 , Thanks for sharing
I got this error by using conditional logic inside a named slot in a base container template, like this:
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.
Use case is like this:
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: https://github.com/nuxt/framework/pull/9478.