---
title: "Nuxt and Hydration"
description: "Why fixing hydration issues is important"
canonical_url: "https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/guide/best-practices/hydration"
---
# Nuxt and Hydration

> Why fixing hydration issues is important

When developing, you may face hydration issues. Don't ignore those warnings.

## Why Is It Important to Fix Them?

Hydration mismatches are not just warnings - they are indicators of serious problems that can break your application:

### Performance Impact

- **Increased time to interactive**: Hydration errors force Vue to re-render the entire component tree, which will increase the time for your Nuxt app to become interactive
- **Poor user experience**: Users may see content flashing or unexpected layout shifts

### Functionality Issues

- **Broken interactivity**: Event listeners may not attach properly, leaving buttons and forms non-functional
- **State inconsistencies**: Application state can become out of sync between what the user sees and what the application thinks is rendered
- **SEO problems**: Search engines may index different content than what users actually see

## How to Detect Them

### Development Console Warnings

Vue will log hydration mismatch warnings in the browser console during development:

![Screenshot of Vue hydration mismatch warning in the browser console](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/assets/docs/best-practices/vue-console-hydration.png)

## Common Reasons

### Browser-only APIs in Server Context

**Problem**: Using browser-specific APIs during server-side rendering.

```html
<template>
  <div>User preference: {{ userTheme }}</div>
</template>

<script setup>
// This will cause hydration mismatch!
// localStorage doesn't exist on the server!
const userTheme = localStorage.getItem('theme') || 'light'
</script>
```

**Solution**: You can use [`useCookie`](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/api/composables/use-cookie):

```html
<template>
  <div>User preference: {{ userTheme }}</div>
</template>

<script setup>
// This works on both server and client
const userTheme = useCookie('theme', { default: () => 'light' })
</script>
```

### Inconsistent Data

**Problem**: Different data between server and client.

```html
<template>
  <div>{{ Math.random() }}</div>
</template>
```

**Solution**: Use SSR-friendly state:

```html
<template>
  <div>{{ state }}</div>
</template>

<script setup>
const state = useState('random', () => Math.random())
</script>
```

### Conditional Rendering Based on Client State

**Problem**: Using client-only conditions during SSR.

```html
<template>
  <div v-if="window?.innerWidth > 768">
    Desktop content
  </div>
</template>
```

**Solution**: Use media queries or handle it client-side:

```html
<template>
  <div class="responsive-content">
    <div class="hidden md:block">Desktop content</div>
    <div class="md:hidden">Mobile content</div>
  </div>
</template>
```

### Third-party Libraries with Side Effects

**Problem**: Libraries that modify the DOM or have browser dependencies (this happens a LOT with tag managers).

```html
<script setup>
if (import.meta.client) {
    const { default: SomeBrowserLibrary } = await import('browser-only-lib')
    SomeBrowserLibrary.init()
}
</script>
```

**Solution**: Initialise libraries after hydration has completed:

```html
<script setup>
onMounted(async () => {
  const { default: SomeBrowserLibrary } = await import('browser-only-lib')
  SomeBrowserLibrary.init()
})
</script>
```

### Dynamic Content Based on Time

**Problem**: Content that changes based on current time.

```html
<template>
  <div>{{ greeting }}</div>
</template>

<script setup>
const hour = new Date().getHours()
const greeting = hour < 12 ? 'Good morning' : 'Good afternoon'
</script>
```

**Solution**: Use [`NuxtTime`](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/api/components/nuxt-time) component or handle it client-side:

```html
<template>
  <div>
    <NuxtTime :datetime="new Date()" format="HH:mm" />
  </div>
</template>
```

```html
<template>
  <div>
    <ClientOnly>
      {{ greeting }}
      <template #fallback>
        Hello!
      </template>
    </ClientOnly>
  </div>
</template>

<script setup>
const greeting = ref('Hello!')

onMounted(() => {
  const hour = new Date().getHours()
  greeting.value = hour < 12 ? 'Good morning' : 'Good afternoon'
})
</script>
```

### Phone Numbers on iOS Safari

**Problem**: iOS Safari detects phone numbers (and other data such as dates, addresses and email addresses) in the rendered text and wraps them in links. This changes the DOM before Vue hydrates it, causing a hydration mismatch on iOS only.

**Solution**: Disable detection with the `format-detection` meta tag in your `nuxt.config`:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  app: {
    head: {
      meta: [
        { name: 'format-detection', content: 'telephone=no, date=no, email=no, address=no' },
      ],
    },
  },
})
```

To keep a phone number tappable, wrap it in an `<a href="tel:...">` element so the link is part of the server-rendered HTML.

## In Summary

1. **Use SSR-friendly composables**: [`useFetch`](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/api/composables/use-fetch), [`useAsyncData`](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/api/composables/use-async-data), [`useState`](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/api/composables/use-state)
2. **Wrap client-only code**: Use [`ClientOnly`](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/docs/4.x/api/components/client-only) component for browser-specific content
3. **Consistent data sources**: Ensure server and client uses the same data
4. **Avoid side effects in setup**: Move browser-dependent code to `onMounted`

::tip
You can read the [Vue documentation on SSR hydration mismatch](https://vuejs.org/guide/scaling-up/ssr#hydration-mismatch) for a better understanding of hydration.
::


## Sitemap

See the full [sitemap](https://nuxt-git-fix-nuxter-blog-posts-order-nuxt-js.vercel.app/sitemap.md) for all pages.
