Гидрация и hydration mismatch

↑ 7.5 SSR и Nuxt · ← Предыдущая · → Следующая

Желательно~3 мин чтенияУровень: senior

Зачем это на собесе

Hydration mismatch — самая частая проблема SSR; важно уметь объяснить причину и починить.

Гидрация

Сервер отдаёт HTML, клиент запускает то же приложение и сопоставляет vnode с существующим DOM, не пересоздавая его, добавляет обработчики и реактивность.

Mismatch

Ошибка возникает, когда дерево на клиенте отличается от серверного HTML.

Частые причины:

ПричинаПример
Данные, зависящие от средыwindow, localStorage, ширина экрана
Время и случайностьnew Date(), Math.random(), Date.now()
Таймзона и локальразный формат даты на сервере и клиенте
Невалидный HTML<p> внутри <p>, <div> в <p>, браузер меняет структуру
Разные данныесервер и клиент получили разные ответы API
Расширения браузеравставляют узлы в DOM

Как чинить

<script setup lang="ts">
const now = ref('')
onMounted(() => { now.value = new Date().toLocaleString() })   // только на клиенте
</script>
 
<template>
  <ClientOnly>              <!-- Nuxt -->
    <ChartWidget />
    <template #fallback><Skeleton /></template>
  </ClientOnly>
</template>
  • состояние, зависящее от браузера, инициализировать в onMounted;
  • передавать серверное состояние клиенту (Nuxt useState, Pinia state в window.__INITIAL_STATE__);
  • время и локаль форматировать в одной таймзоне;
  • useId() для стабильных идентификаторов;
  • data-allow-mismatch (Vue 3.5) точечно подавляет предупреждение.

Ленивая гидрация (Vue 3.5)

const Comments = defineAsyncComponent({
  loader: () => import('./Comments.vue'),
  hydrate: hydrateOnVisible(),   // гидрируем, когда блок попал в viewport
})

Также hydrateOnIdle, hydrateOnInteraction, hydrateOnMediaQuery.

Стоимость

Гидрация выполняет весь JS ещё раз: «uncanny valley» — страница видна, но не отвечает. Уменьшают бандл, ленивая гидрация, острова.

Вопросы с ответами