Как устранить ошибку гидратации в Next.js

Как устранить ошибку гидратации в Next.js

Если вы недавно перешли с 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() для получения статуса аутентификации на стороне сервера.

Счастливого кодинга! 🎈🎈

Связаться со мной

@gorlovfrontend