Содержание
Если вы недавно перешли с React на Next.js, то наверняка сталкивались с такой ошибкой: ”Ошибка: Hydration failed because the initial UI does not match what was rendered on the server”. Давайте разберемся, почему обычно возникает эта ошибка и как ее решить.
Что такое Hydration?
В Next.js первый рендер страницы происходит на сервере. Затем этот предварительно отрендеренный HTML передается клиенту. На стороне клиента React берет на себя управление HTML-страницей, чтобы сделать ее интерактивной (по сути, он прикрепляет обработчики событий к узлам dom). Это и есть Hydration.
Почему возникает эта ошибка?
Во время этого процесса React также имеет модель того, как должен выглядеть предварительно отрендеренный HTML (чтобы он мог прикрепить обработчики событий для необходимой Js-интерактивности), если клиентская версия начального рендера не совпадает с предварительно отрендеренным HTML с сервера, мы получаем эту ошибку.
Некоторые сценарии, в которых может возникнуть эта ошибка:
-
Вы используете API браузера для условного рендеринга вашего компонента/страницы: В этом случае, поскольку API браузера (localStorage,sessionStorage) недоступны на сервере, предварительно отрендеренный HTML может отличаться от клиентского.
// Я буду использовать этот атом в разделе решений :) const defaultState={ имя пользователя:"", id:"", isLoggedIn:false } export const adminState=atom<IAdmin>({ key: "currentAdmin", default: (typeof window !== 'undefined') ? ( localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') as string) : defaultState ) : defaultState }) //если это состояние используется для условного рендеринга, то это приведет к ошибке гидратацииПри использовании этого состояния отдачи в компоненте возникнет ошибка гидратации, так как на сервере HTML будет отрисовываться в соответствии с состоянием по умолчанию, а на стороне клиента HTML будет другим из-за localStorage.
-
Ваш синтаксис HTML не корректен: В этом случае, вероятно, вы использовали JSX каким-то странным образом. некоторые примеры :
<div>logout</div> Ваш текст -
Некоторые расширения браузеров изменяют HTML на стороне клиента.
Решения для данной ошибки:
-
Переконфигурируйте логику с useEffect:. Сервер игнорирует блоки useEffect при рендеринге HTML на стороне сервера. useEffect работает только на стороне клиента и имеет доступ ко всем API браузера.
// Я использую упомянутый выше атом состояния const RequireAuth: React.FC<{ children: ReactElement }> = ({ children }) => { const [loader, setLoader] = useState(true); const user = useRecoilValue(userState); const router = useRouter(); useEffect(() => { if (user.isLoggedIn) { setLoader(false); } else router.replace('/login') }, []); return <>{!loader && children}</>; }; // Поскольку user.isLoggedIn зависит от localStorage, // мы проверяем его наличие в useEffect (на стороне клиента) -
Отключаем SSR на некоторых компонентах, которые могут отличаться на стороне клиента:
import dynamic from 'next/dynamic'; //код для YourComponent export default dynamic(() => Promise.resolve(YourComponent), { ssr: false }); -
В случае с временными метками предварительно отрендеренный HTML будет отличаться от клиентской версии. В таких случаях можно подавить предупреждение, используя в элементе suppressHydrationWarning={true}.
Дополнительный совет: Вместо того чтобы рендерить всю страницу на стороне клиента, подумайте о том, чтобы абстрагировать части, требующие рендеринга на стороне клиента, в отдельные компоненты. Для их рендеринга используйте описанные выше методы, внеся в них некоторые изменения.
Примечание по безопасности: В приведенной выше статье я храню токен JWT в localStorage, что не является хорошей практикой. Лучшим решением может быть аутентификация с помощью cookies. Если вы используете cookies, то можете попробовать использовать функцию getServerSideProps() для получения статуса аутентификации на стороне сервера.
Счастливого кодинга! 🎈🎈