---
title: Understand and Fix SSR Hydration Mismatches
impact: HIGH
impactDescription: Hydration mismatches cause visual flickering, performance loss, and broken functionality
type: gotcha
tags: [vue3, ssr, hydration, debugging, nuxt, server-side-rendering]
---
# Understand and Fix SSR Hydration Mismatches
**Impact: HIGH** - Hydration mismatches occur when the HTML rendered on the client differs from what the server rendered. Vue attempts to recover by discarding and re-rendering mismatched nodes, causing performance degradation, visual flickering, and potentially broken event handlers.
Understanding the common causes helps you prevent and debug these issues effectively.
## Task Checklist
- [ ] Validate HTML structure for proper nesting (no div in p, no nested a tags)
- [ ] Move random value generation to onMounted or use seeded randoms
- [ ] Format dates/times on client side only
- [ ] Use `data-allow-mismatch` (Vue 3.5+) for intentional mismatches
- [ ] Check for browser-modified HTML in dev tools
## Cause 1: Invalid HTML Nesting
Browsers auto-correct invalid HTML, creating different DOM than Vue expects.
**Incorrect:**
```vue
This will break hydration
Nested link
Block in inline
```
Browser converts the first example to:
```html
This will break hydration
```
**Correct:**
```vue
Nested action
```
## Cause 2: Random Values in Render
Server and client generate different random values.
**Incorrect:**
```vue
Form field
{{ item.name }}
```
**Correct - Client-Only Random:**
```vue
Form field
{{ item.name }}
```
**Correct - Seeded Random:**
```javascript
// utils/seededRandom.js
export function createSeededRandom(seed) {
return function() {
seed = (seed * 9301 + 49297) % 233280
return seed / 233280
}
}
// Use same seed on server and client
const seed = 12345 // Could be based on user ID, page, etc.
const random = createSeededRandom(seed)
```
## Cause 3: Timezone and Date Differences
Server may be in different timezone than client.
**Incorrect:**
```vue
{{ new Date().toLocaleTimeString() }}
{{ formatDate(article.createdAt) }}
```
**Correct:**
```vue
{{ displayTime || 'Loading...' }}
{{ formattedDate }}
```
## Cause 4: Browser Extensions and Modifications
Browser extensions can inject content into the DOM.
**Mitigation:**
```vue
{{ pageTitle }}
```
## Vue 3.5+ Suppressing Intentional Mismatches
```vue
{{ clientOnlyText }}
```
Valid `data-allow-mismatch` values:
- `text` - Text content mismatches
- `children` - Child element mismatches
- `class` - Class attribute mismatches
- `style` - Style attribute mismatches
- `attribute` - Other attribute mismatches
- (no value) - All mismatches
## Debugging Hydration Mismatches
```javascript
// Enable detailed hydration mismatch warnings in development
// vite.config.js
export default {
define: {
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
}
}
```
```vue
```
## Common Error Messages
| Error | Likely Cause |
|-------|--------------|
| "Hydration text content mismatch" | Different text on server/client (dates, random) |
| "Hydration children mismatch" | Invalid HTML nesting, conditional rendering |
| "Hydration attribute mismatch" | Dynamic attributes with different values |
| "Hydration node mismatch" | Completely different elements rendered |
## Reference
- [Vue.js SSR Guide - Hydration Mismatch](https://vuejs.org/guide/scaling-up/ssr.html#hydration-mismatch)
- [Nuxt Hydration Best Practices](https://nuxt.com/docs/guide/best-practices/hydration)
- [data-allow-mismatch RFC](https://github.com/vuejs/core/pull/9562)