Что такое гидратация React и почему она важна для SEO
Гидратация React — это подключение клиентского React к HTML, который уже был сформирован на сервере или во время статической генерации. Она превращает готовую HTML-разметку в интерактивное приложение, но требует совпадения серверного HTML и первого клиентского рендера. Если результат различается, React сообщает об ошибке гидратации, а часть страницы может быть перерисована в браузере. Для SEO гидратация важна потому, что она не должна заменять доступный поисковику текст, заголовки, ссылки и метаданные: сначала страница должна отдать полноценный HTML, а затем React — добавить интерактивность.
Содержание
- Что такое гидратация React
- Чем гидратация отличается от рендера
- Как проходит гидратация
- Почему возникает hydration mismatch
- Какие ошибки влияют на SEO
- Как работать с клиентскими данными
- Progressive enhancement и React
- Как диагностировать проблему
- Когда гидратация не нужна
- FAQ
- Чек-лист
Что такое гидратация React
В клиентском React-приложении используется createRoot. Браузер получает HTML-документ, загружает JavaScript, React создаёт дерево компонентов и записывает его в контейнер приложения.
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")!).render(<App />);
При серверном рендеринге или статической генерации HTML появляется раньше. Сервер или сборщик формирует разметку React, а браузер получает уже заполненную страницу. В этом случае нельзя снова бездумно вызывать createRoot, потому что он предназначен для клиентского рендера нового дерева. Для уже существующей серверной разметки используется hydrateRoot.
import { hydrateRoot } from "react-dom/client";
import App from "./App";
hydrateRoot(document.getElementById("root")!, <App />);
React определяет гидратацию как подключение компонентов к HTML, который ранее был создан React на сервере. React не должен заново строить всю разметку с нуля: он использует существующие узлы и подключает к ним обработчики событий и внутреннюю логику компонентов.
Гидратация не создаёт содержание страницы автоматически. Если сервер отдал пустой контейнер, hydrateRoot не превращает его в полноценный серверный HTML. Для пустого клиентского контейнера используется обычный клиентский рендеринг. Гидратация имеет смысл только тогда, когда внутри корневого элемента уже находится разметка, созданная сервером или статическим генератором.
Гидратация и рендеринг
Рендеринг — это формирование результата компонента. Гидратация — это последующее подключение клиентского React к уже сформированному результату.
Упрощённая последовательность для SSR или SSG выглядит так:
- Сервер или сборщик выполняет React-код.
- Получается HTML страницы.
- HTML отправляется браузеру или сохраняется как статический файл.
- Браузер отображает текст и структуру.
- Загружается клиентский JavaScript.
hydrateRootсопоставляет React-дерево с существующей разметкой.- Кнопки, формы, меню и другие интерактивные элементы начинают работать.
Это разделение важно для SEO. Поисковый робот может получить HTML на первом этапе, ещё до завершения клиентской загрузки. Поэтому основной текст статьи, H1, обычные ссылки и важные метаданные не должны появляться только после гидратации.
Гидратация также влияет на время, через которое интерфейс становится интерактивным. Страница может быть видна пользователю, но обработчик кнопки ещё не подключён, пока браузер не загрузит и не выполнит необходимый JavaScript. Чем больше клиентский код и чем тяжелее компоненты, тем больше работы приходится выполнить браузеру.
Для сайта услуг это означает, что первый HTML должен содержать объяснение услуги, преимущества, условия, контакты и основные ссылки. Интерактивная форма, маска телефона, калькулятор и фильтр могут подключаться позже, если без них остаётся доступным основной сценарий страницы.
Как работает hydrateRoot
Функция hydrateRoot принимает DOM-узел с серверной разметкой и React-элемент, который должен воспроизвести тот же результат.
import { hydrateRoot } from "react-dom/client";
import App from "./App";
const container = document.getElementById("root");
if (container) {
hydrateRoot(container, <App />);
}
React ожидает, что дерево, переданное в hydrateRoot, создаст такой же результат, который был получен на сервере. Это относится к тексту, структуре элементов и значениям атрибутов. Несовпадения следует исправлять как ошибки приложения, а не маскировать без проверки.
Пример безопасного серверного и клиентского результата:
function ArticleHeader({ title }: { title: string }) {
return <h1>{title}</h1>;
}
Если сервер и клиент получают одинаковый title, компонент создаёт одинаковый H1.
Проблемный вариант:
function ArticleHeader() {
return <h1>{window.innerWidth > 768 ? "Статья" : "Материал"}</h1>;
}
На сервере объекта window нет. Кроме того, сервер не знает ширину окна конкретного посетителя. Даже если код защищён проверкой существования window, сервер и первый клиентский рендер могут вернуть разные заголовки. Это создаёт mismatch и делает результат недетерминированным.
Ещё один проблемный пример:
function PublishedAt() {
return <time>{new Date().toLocaleString()}</time>;
}
Время и локаль могут отличаться между сервером и браузером. Сервер может находиться в другом часовом поясе, а момент выполнения будет разным. Если значение нужно показать только в браузере, его следует получать после успешной гидратации.
Почему возникает hydration mismatch
Hydration mismatch означает, что серверная разметка и результат первого клиентского рендера не совпали. React и фреймворки на его основе могут вывести предупреждение или ошибку в консоль. Поведение при восстановлении зависит от масштаба различий, но рассчитывать на автоматическое исправление всех несовпадений нельзя.
Основные причины:
- использование
window,document,localStorageилиmatchMediaво время рендера; - вывод
Date.now(),new Date()илиMath.random(); - разные данные на сервере и клиенте;
- различающаяся локаль или часовой пояс;
- неправильная вложенность HTML-элементов;
- различия из-за случайного состояния;
- сторонний скрипт или расширение браузера изменили DOM;
- CDN или оптимизатор изменил HTML;
- сервер и клиент используют разные версии данных;
- разные условия отображения для мобильного и настольного экрана.
Браузерные API
Серверный код не должен обращаться к браузерным объектам во время построения HTML. Переносить такой доступ в useEffect недостаточно, если сам компонент всё равно сначала формирует разный результат.
Надёжный шаблон — одинаковое начальное значение и изменение после монтирования:
import { useEffect, useState } from "react";
export function BrowserWidth() {
const [width, setWidth] = useState<number | null>(null);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
return <span>{width === null ? "Определяется" : `${width} px`}</span>;
}
Сервер и первый клиентский рендер показывают «Определяется». После гидратации эффект получает ширину окна и обновляет компонент.
Нестабильные значения
Случайные числа, текущая дата и другие нестабильные значения не должны использоваться для формирования серверного и первого клиентского HTML. Если значение нужно для отображения, его можно передать с сервера как часть данных страницы или вычислить после монтирования.
Для идентификаторов React рекомендует использовать useId, когда компоненту нужен согласованный ID для связи подписи и поля формы. Самостоятельная генерация ID через случайное число может привести к несовпадению.
Разные данные
Сервер может отрендерить статью из одного состояния CMS, а клиент сразу запросить другое состояние API. Если результат меняется до завершения гидратации, React увидит различие.
Для стабильного первого рендера данные страницы нужно передавать в клиент в том же состоянии, которое использовалось сервером. Обновление можно запускать после гидратации, когда начальный HTML уже согласован.
Некорректная HTML-структура
Браузер исправляет некоторые ошибки вложенности HTML до того, как React получает DOM для гидратации. Например, интерактивные элементы нельзя вкладывать друг в друга, а структура абзацев и блочных элементов должна соответствовать правилам HTML.
Проверяйте JSX-компоненты, которые генерируют:
- вложенные ссылки;
- вложенные кнопки;
divвнутриp;- повторное использование компонентов с несовместимой оболочкой;
- разный набор элементов при одинаковых данных.
Влияние на SEO
Сама ошибка гидратации не является отдельным фактором ранжирования. Её значение для SEO связано с последствиями: часть страницы может перерисоваться, содержание может исчезнуть, текст может появиться только после JavaScript, а элементы интерфейса могут работать нестабильно.
На SEO-важной странице следует разделять критическое содержание и клиентскую интерактивность.
К критическому содержанию относятся:
- заголовок страницы;
- основной текст;
- H1 и тематические подзаголовки;
- внутренние ссылки;
- хлебные крошки;
- изображения, важные для понимания материала;
- контактные данные;
- сведения об услуге;
- структурированные данные, соответствующие видимому содержанию.
К интерактивному слою относятся:
- раскрывающиеся панели;
- фильтры;
- калькуляторы;
- маски ввода;
- отправка формы без перезагрузки;
- сортировка;
- переключатели интерфейса;
- личные настройки пользователя.
Если критическое содержание присутствует в серверном HTML, поисковик получает его независимо от того, когда завершится клиентская гидратация. Если содержание создаётся только клиентом, его доступность зависит от загрузки JavaScript, выполнения кода и получения данных.
Гидратация также может повлиять на доступность ссылок. Ссылка должна присутствовать как обычный элемент a с атрибутом href, а не появляться только после клика или заменяться кнопкой с вызовом навигации.
Работа с клиентскими данными
Данные, зависящие от браузера, нужно отделять от данных, необходимых для индексации. Например, текст статьи должен приходить из источника контента во время серверного рендера или сборки. Состояние открытого меню может определяться уже в браузере.
Пример компонента с клиентским состоянием:
import { useState } from "react";
export function Details() {
const [open, setOpen] = useState(false);
return (
<section>
<button
type="button"
aria-expanded={open}
=> setOpen(value => !value)}
>
Показать подробности
</button>
{open && <div>Дополнительная информация</div>}
</section>
);
}
Если дополнительный текст важен для понимания страницы и поисковой видимости, не следует без необходимости скрывать его только за кликом. Если это второстепенная часть интерфейса, клиентское раскрытие может быть оправдано.
Для формы важно, чтобы на странице оставались обычные подписи, названия полей и способы связи. JavaScript может улучшить отправку и валидацию, но не должен превращать всю страницу в пустое приложение.
Progressive enhancement
Progressive enhancement — подход, при котором сначала создаётся базовый работающий уровень содержания и функций, а затем добавляются улучшения для браузеров и устройств с поддержкой необходимого кода.
Для React-сайта это можно реализовать так:
- сервер отдаёт содержательный HTML;
- пользователь видит текст, навигацию и базовую форму;
- React подключает обработчики и расширенные сценарии;
- дополнительные функции загружаются только там, где они нужны.
Такой подход снижает зависимость публичной страницы от JavaScript. Он также помогает разделить SEO-содержание и интерактивность. Статья остаётся доступной, даже если клиентский скрипт загрузился с задержкой или завершился ошибкой.
Progressive enhancement не означает полный отказ от React. React может отвечать за сложные элементы интерфейса, но его роль должна соответствовать задаче страницы. Для блога основной документ может быть статическим, а комментарии, поиск по материалам и формы — интерактивными.
Диагностика гидратации
Просмотр исходного HTML
Откройте исходный код страницы, а не только панель Elements в DevTools. В исходном HTML проверьте наличие:
- H1;
- заголовка статьи;
- основного текста;
- внутренних ссылок;
- title;
- canonical;
- структурированных данных, если они предусмотрены.
Панель Elements показывает DOM после работы JavaScript и может скрыть то, что отсутствовало в первоначальном ответе сервера.
Консоль браузера
Ошибки гидратации обычно появляются в Console. Зафиксируйте компонент и фрагмент разметки, на котором возникает несовпадение. Не следует сразу добавлять suppressHydrationWarning: сначала найдите причину.
Сравнение сервера и клиента
Сравните HTML, полученный через просмотр исходного кода или curl, с DOM после выполнения JavaScript. Ищите различия в тексте, атрибутах, ссылках, количестве элементов и порядке блоков.
curl -Ls https://example.ru/blog/article/ > server.html
Для поиска заголовка:
grep -i "<h1" server.html
Проверка без JavaScript
Отключение JavaScript в браузере не имитирует полностью работу поискового робота, но помогает увидеть, является ли HTML самостоятельным документом. Если при отключённом JavaScript исчезает весь основной текст, публичная страница слишком сильно зависит от клиентского рендера.
Инструменты поисковых систем
Проверяйте URL в инструментах поисковых систем и сравнивайте исходный и отрендеренный результат. Дополнительно анализируйте серверные логи: обращения роботов, HTTP-коды, ошибки ресурсов и запросы к маршрутам.
Когда гидратация не нужна
Гидратация не требуется, если страница полностью статична и не содержит React-интерактивности. Статический HTML может обслуживаться без клиентского JavaScript.
Она также не нужна для части проекта, которая изначально является закрытым серверным документом без клиентской логики. Однако личный кабинет или сложная панель управления обычно требуют клиентского JavaScript, поэтому там применяются другие схемы рендера.
Не следует добавлять гидратацию ко всему сайту только потому, что проект написан на React. Если компонент выводит только статический текст и не имеет интерактивного поведения, его можно оставить серверным или статическим. Это уменьшает объём JavaScript, который браузер должен загрузить и выполнить.
Гидратация не подходит как исправление для пустого CSR-сайта. Если сервер не формирует содержательный HTML, сначала нужно изменить стратегию рендеринга: использовать SSR, SSG или пререндеринг для публичных маршрутов.
Чек-лист перед публикацией
- Публичная страница отдаёт основной текст в исходном HTML.
- Для серверной разметки используется
hydrateRoot, а неcreateRoot. - Серверный и первый клиентский рендер формируют одинаковый результат.
- В рендеринге нет
window,document,localStorageиmatchMediaбез защиты. - В исходном HTML нет случайных значений и неконтролируемой текущей даты.
- Данные сервера и клиента синхронизированы до гидратации.
- JSX не создаёт неправильную вложенность HTML.
- Важные ссылки используют
a href. - H1, title, canonical и основной текст доступны до выполнения JavaScript.
- Ошибки гидратации отсутствуют в консоли.
-
suppressHydrationWarningне используется как универсальная заглушка. - Интерактивные компоненты не скрывают обязательное содержание.
- Страница проверена в исходном HTML и в отрендеренном DOM.
- Проверены мобильная версия и отключённый JavaScript.
- URL проверен в инструментах поисковых систем.
- После деплоя проверены логи и HTTP-ответ страницы.
Итог
Гидратация нужна для превращения серверной или статически созданной React-разметки в интерактивное приложение. Она не заменяет серверный рендеринг и не решает проблему пустого HTML: сначала публичная страница должна получить содержательный документ, а затем React может подключить поведение.
Главное техническое требование — серверная разметка и первый клиентский рендер должны совпадать. Браузерные API, случайные значения, текущее время, разные данные и неправильная HTML-вложенность часто приводят к hydration mismatch. Ошибки нужно исправлять, а не скрывать предупреждениями.
Для блога и сайта услуг Like Sites практичная схема — отдавать статьи, заголовки, ссылки и коммерческое содержание через SSG, пререндеринг или SSR, а React использовать для форм, меню, поиска, фильтров и других интерактивных компонентов. Это сохраняет доступность HTML для поисковиков и не заставляет весь сайт зависеть от клиентского JavaScript.
Официальные материалы
- React: hydrateRoot
- React: renderToString
- React: renderToPipeableStream
- Google Search Central: основы JavaScript SEO
- MDN: Progressive enhancement

