エラー: 初期のUIとサーバーでレンダリングされた内容が一致しないため、ハイドレーションに失敗しました
バグを遭遇した場所
export default function Nav() {
const user = getUser();
return (
{user ? <AuthenticatedNav /> : <UnauthenticatedNav />} // この記述でエラーが発生
);
}
このエラー通知は問題のあるコードにつながる情報を提供しませんが、コンソールは提供します。
でも、なぜ?
このエラーの原因は?
アプリケーションをレンダリングしている間に、事前にレンダリングされた(SSR/SSG)Reactツリーと、ブラウザでの最初のレンダリング時にレンダリングされたReactツリーの間に差異がありました。最初のレンダリングはハイドレーションと呼ばれるReactの機能です。
これによりReactツリーがDOMと同期せず、意図しないコンテンツや属性が存在する可能性があります。 -nextjsドキュメント
うん、なんとなくわかるけど、でもなぜ?
やりたいことはこれだけ
ユーザーがログインしていれば<AuthenticatedNav>
コンポーネントをレンダリングし、そうでなければ<UnauthenticatedNav>
コンポーネントをレンダリングする。
「ハイドレーション」とは何か、なぜ使用されるのかを理解しよう
サーバーサイドレンダリング(SSR)は、nextjsなどのフレームワークが、パフォーマンス(LCP&FCP)とユーザーエクスペリエンス(SEO)を向上させるために使用し、アプリを最初にサーバー上でレンダリングします。これは完全に形成されたHTMLドキュメントをユーザーに返しますが、アプリは「動的」であり、すべてがHTML&CSSで達成できるわけではありません。したがって、アプリをインタラクティブにするために、ReactにHTMLへのイベントハンドラーを添付するよう指示します。
コンポーネントをレンダリングし、イベントハンドラーを添付するこのプロセスを「ハイドレーション」と呼びます。これは、「乾燥した」HTMLに「相互作用性(JS)の水」を注ぐようなものです。ハイドレーション後、アプリケーションはインタラクティブまたは「動的」になります。
ハイドレーションと再ハイドレーションはしばしば同義で使用されますが、再ハイドレーションでは、クライアントサイドのJSはコンパイル時に生成されたのと同じReactコードを含みます。
このコードはユーザーのデバイス上で実行され、世界がどのように見えるべきかの画像を構築します。その後、それをドキュメントに組み込まれたHTMLと比較します。これは再ハイドレーションと呼ばれるプロセスです。
再ハイドレーションでは、ReactはDOMが変更されないと想定しています。それは、既存のDOMに適応しようとしています。
Reactアプリが再ハイドレーションを行うとき、DOM構造が一致することを前提としています。一致しない場合は、ご存知の通りです。
ですので、私たちの場合、サーバー上ではuser
状態を知ることができず、クライアントにマウントされるまでuser
状態はundefinedを返します。
では、この問題をどう解決するか
基本的には、以下の2つの方法で修正できます。
useEffect
/useMounted
フック- コンポーネントでラッピングする
useEffect
か useMounted
カスタムフック
export default function Nav() {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
const user = getUser();
return (
{user ? <AuthenticatedNav /> : <UnauthenticatedNav />}
);
}
または
useHasMounted
カスタムフック
function useHasMounted() {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
コンポーネントでラッピングする
<clientOnly>
コンポーネント
function ClientOnly({ children, ...delegated }) {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return (
<div {...delegated}>
{children}
</div>
);
}
その後、クライアントサイドでのみレンダリングすることを望むコンポーネントをラッピングできます。
<ClientOnly>
<Nav/>
</ClientOnly>
僕がしたこと
nav.js
import { UserState } from '../../context/userProvider'
export default function Nav() {
return (
UserState() ? <AuthenticatedNav /> : <UnauthenticatedNav />
);
}
userProvider.js
//グローバルユーザーコンテキストプロバイダーを使用
export function UserState() {
const { user } = useUser();
const [isUserLoggedIn, setIsUserLoggedIn] = useState(false);
useEffect(() => {
setIsUserLoggedIn(!!user);
}, [user]);
return isUserLoggedIn;
}
🙏読んでくださってありがとうございます。この記事が参考になれば幸いです。批判、提案、または訂正があれば、コメントセクションでお知らせください。
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/rkjain119/error-hydration-failed-because-the-initial-ui-does-not-match-what-was-rendered-on-the-server-1d0f