Server Rendering Errors in React 18: part 2



Окей, а что же там с 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