Server Rendering Errors in React 18: part 2
Окей, а что же там с missmatch'ами?
На клиенте мы вызываем ReactDOM.hydrate. Этот метод не рендерит приложение, а только навешивает слушателей на разметку, полученную с сервера. Это позволяет инициализировать приложение в разы быстрее. Таким образом пользователь сразу же видит контент, а не белый экран. Тем самым мы улучшаем метрику Time to Interactive.
Возьмем простой компонент:
В таком случае всё работает отлично: контент одинаковый — никаких ошибок.
Но что происходит если контент на клиенте и сервере разный? Ну, скажем, отобразим текущее время:
Реакт умеет эффективно обрабатывать кейсы, когда у элемента меняется только текст. Т.е. заменяет текст на актуальный без особых проблем для производительности.
А что если поменяется не только текст, но и разметка? На сервере будем рендерить один и тот же текст в h1, а на клиенте в h2?
В таком случае реакт ведёт себя непредсказуемо и чем сильнее отличается разметка, тем хуже результат.
Итого: разметка должна быть одинаковой и на сервере, и на клиенте. Если разметка отличается, то реакт выплюнет ошибку, но только в dev-режиме.
И если такой ворнинг ещё реально найти, т.к. h1 обычно немного на странице, а вот условный <div> in <div> попробуй отыщи.
Но главная проблема в том, что в проде эту ошибку невозможно поймать. Ещё 2.5 года назад мне показалось это очень странным и я открыл ишью Output hydration warning in production mode. Ответ был незамысловатый: «сбилди приложение в dev-режиме, налогируй ошибок и чини». И это, в общем-то, рабочий вариант, но только dev-режим реакта медленее и весит существенно больше.
К счастью, и у этой проблемы теперь есть решение! Начиная с React 18, в случае несовпадения разметки, она заменяется начиная с ближайшего Suspense. Таким образом мы избавляемся от артефактов с поехвашей разметкой.
А новый метод hydrateRoot поддерживает опцию
Подробней тут → RFC: Server Rendering Errors in React 18
Окей, а что же там с missmatch'ами?
На клиенте мы вызываем ReactDOM.hydrate. Этот метод не рендерит приложение, а только навешивает слушателей на разметку, полученную с сервера. Это позволяет инициализировать приложение в разы быстрее. Таким образом пользователь сразу же видит контент, а не белый экран. Тем самым мы улучшаем метрику Time to Interactive.
Возьмем простой компонент:
<div>
<h1>Я заголовок</h1>
</div>
В таком случае всё работает отлично: контент одинаковый — никаких ошибок.
Но что происходит если контент на клиенте и сервере разный? Ну, скажем, отобразим текущее время:
<div>
Я заголовок сгенерированный в {Date.now()}
</div>
Реакт умеет эффективно обрабатывать кейсы, когда у элемента меняется только текст. Т.е. заменяет текст на актуальный без особых проблем для производительности.
А что если поменяется не только текст, но и разметка? На сервере будем рендерить один и тот же текст в h1, а на клиенте в h2?
<div>
{window ? (
<h2>Я заголовок</h2
) : (
<h1>Я заголовок</h1>
)}
</div>
В таком случае реакт ведёт себя непредсказуемо и чем сильнее отличается разметка, тем хуже результат.
Итого: разметка должна быть одинаковой и на сервере, и на клиенте. Если разметка отличается, то реакт выплюнет ошибку, но только в dev-режиме.
Warning: Text content did not match. Server: "0" Client: "5"
И если что-то посложнее, то:Warning: Did not expect server HTML to contain a <h1> in <div>.
И если такой ворнинг ещё реально найти, т.к. h1 обычно немного на странице, а вот условный <div> in <div> попробуй отыщи.
Но главная проблема в том, что в проде эту ошибку невозможно поймать. Ещё 2.5 года назад мне показалось это очень странным и я открыл ишью Output hydration warning in production mode. Ответ был незамысловатый: «сбилди приложение в dev-режиме, налогируй ошибок и чини». И это, в общем-то, рабочий вариант, но только dev-режим реакта медленее и весит существенно больше.
К счастью, и у этой проблемы теперь есть решение! Начиная с React 18, в случае несовпадения разметки, она заменяется начиная с ближайшего Suspense. Таким образом мы избавляемся от артефактов с поехвашей разметкой.
А новый метод hydrateRoot поддерживает опцию
onRecoverableError
, с помощью которой можно трекать ошибки гидратации.hydrateRoot(container, <App />, {
onRecoverableError(error) {
// шлём ошибку в условный Sentry
}
})
Подробней тут → RFC: Server Rendering Errors in React 18