Nuxt и гидратация
В разработке вы можете столкнуться с ошибками гидратации. Не игнорируйте эти предупреждения.
Зачем их исправлять?
Расхождения при гидратации — не просто предупреждения: это признак серьёзных проблем, которые могут сломать приложение:
Влияние на производительность
- Дольше до интерактивности: ошибки гидратации заставляют Vue перерисовывать всё дерево компонентов, из‑за чего приложение дольше становится интерактивным.
- Плохой пользовательский опыт: пользователь может видеть мигание контента или неожиданные сдвиги вёрстки.
Проблемы с поведением
- Ломается интерактивность: обработчики могут не навеситься — кнопки и формы перестают работать.
- Рассинхрон состояния: то, что видит пользователь, и то, что «думает» приложение, расходятся.
- Проблемы с поисковой оптимизацией: поисковик может индексировать не то, что видит пользователь.
Как обнаружить
Предупреждения в консоли разработки
Vue выводит предупреждения о несовпадении гидратации в консоли браузера в режиме разработки:

Типичные причины
Браузерные API в контексте сервера
Проблема: использование API, доступных только в браузере, во время SSR.
<template>
<div>Предпочтение пользователя: {{ userTheme }}</div>
</template>
<script setup>
// вызовет несовпадение при гидратации!
// на сервере нет localStorage
const userTheme = localStorage.getItem('theme') || 'light'
</script>
Решение: можно использовать useCookie:
<template>
<div>Предпочтение пользователя: {{ userTheme }}</div>
</template>
<script setup>
// работает и на сервере, и на клиенте
const userTheme = useCookie('theme', { default: () => 'light' })
</script>
Разные данные на сервере и клиенте
Проблема: данные на сервере и клиенте не совпадают.
<template>
<div>{{ Math.random() }}</div>
</template>
Решение: состояние, дружественное к SSR:
<template>
<div>{{ state }}</div>
</template>
<script setup>
const state = useState('random', () => Math.random())
</script>
Условный рендер по состоянию клиента
Проблема: условия, зависящие только от клиента, при SSR.
<template>
<div v-if="window?.innerWidth > 768">
Контент для десктопа
</div>
</template>
Решение: медиазапросы или обработка только на клиенте:
<template>
<div class="responsive-content">
<div class="hidden md:block">Контент для десктопа</div>
<div class="md:hidden">Контент для мобильных</div>
</div>
</template>
Сторонние библиотеки с побочными эффектами
Проблема: библиотеки, меняющие DOM или завязанные на браузер (часто — менеджеры тегов).
<script setup>
if (import.meta.client) {
const { default: SomeBrowserLibrary } = await import('browser-only-lib')
SomeBrowserLibrary.init()
}
</script>
Решение: инициализировать после завершения гидратации:
<script setup>
onMounted(async () => {
const { default: SomeBrowserLibrary } = await import('browser-only-lib')
SomeBrowserLibrary.init()
})
</script>
Динамический контент по времени
Проблема: контент зависит от текущего времени.
<template>
<div>{{ greeting }}</div>
</template>
<script setup>
const hour = new Date().getHours()
const greeting = hour < 12 ? 'Доброе утро' : 'Добрый день'
</script>
Решение: компонент NuxtTime или логика на клиенте:
<template>
<div>
<NuxtTime :date="new Date()" format="HH:mm" />
</div>
</template>
<template>
<div>
<ClientOnly>
{{ greeting }}
<template #fallback>
Здравствуйте!
</template>
</ClientOnly>
</div>
</template>
<script setup>
const greeting = ref('Здравствуйте!')
onMounted(() => {
const hour = new Date().getHours()
greeting.value = hour < 12 ? 'Доброе утро' : 'Добрый день'
})
</script>
Кратко
- Композаблы, дружественные к SSR:
useFetch,useAsyncData,useState. - Код только для браузера: компонент
ClientOnly. - Одинаковые источники данных: сервер и клиент должны получать согласованные данные.
- Без побочных эффектов в setup: браузерный код — в
onMounted.