Back

Next.js: 「Hydration failed because the initial UI does not match」エラーの修正方法

Next.js: 「Hydration failed because the initial UI does not match」エラーの修正方法

Next.jsアプリケーションで「Hydration failed because the initial UI does not match what was rendered on the server」というエラーに遭遇したことがあるなら、あなただけではありません。このReactハイドレーションミスマッチは、サーバーサイドレンダリングアプリケーションを構築する際に開発者が直面する最も一般的な問題の一つです。混乱を解消し、適切に修正していきましょう。

重要なポイント

  • ハイドレーションエラーは、サーバーとクライアントが異なるHTMLをレンダリングする際に発生します
  • ブラウザ専用API、ランダムな値、無効なHTMLのネストが一般的な原因です
  • クライアントサイドロジックには useEffect を使用し、クライアント専用コンポーネントには動的インポートを使用するか、決定論的なレンダリングを確保してください
  • サーバーとクライアント間で初期レンダリングを同一に保つことが重要です

ハイドレーションとは何か、なぜ失敗するのか?

ハイドレーションとは、サーバーでレンダリングされたHTMLにJavaScriptの機能を付加するReactのプロセスです。Next.jsがプリレンダリングされたHTMLをブラウザに送信すると、Reactはサーバー側のHTMLとクライアント側でレンダリングされるものを比較して引き継ぎます。両者が一致しない場合、ハイドレーションエラーが発生します。

これは、Reactがサーバーの作業を二重チェックしているようなものです。初期レンダリングが異なる場合、Reactはイベントハンドラーや状態管理を安全に付加できないため、ハイドレーション修正が必要になります。

Next.jsハイドレーションエラーの一般的な原因

ブラウザ専用APIがSSRを破壊する

Reactサーバーサイドレンダリングの問題で最も頻繁に発生する原因は、初期レンダリング時にブラウザAPIを使用することです:

// ❌ これは失敗します - window はサーバー上に存在しません
function BadComponent() {
  const width = window.innerWidth;
  return <div>Screen width: {width}px</div>;
}

非決定論的な値

ランダムな値やタイムスタンプは、サーバーとクライアント間で異なる出力を生成します:

// ❌ サーバーとクライアントで異なるIDが生成されます
function RandomComponent() {
  return <div id={Math.random()}>Content</div>;
}

条件付きレンダリングの違い

ロジックが異なるHTML構造を生成する場合:

// ❌ mounted 状態がサーバー(false)とクライアント(エフェクト後にtrue)で異なります
function ConditionalComponent() {
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);
  return <div>{mounted ? 'Client' : 'Server'}</div>;
}

無効なHTMLのネスト

ブラウザが自動修正する不正なHTML構造:

<!-- ❌ 無効なネスト -->
<p>
  <div>This breaks hydration</div>
</p>

ハイドレーションエラーの3つの確実な修正方法

修正方法1: useEffectでクライアント専用ロジックをラップする

useEffect フックはハイドレーション完了後に実行されるため、ブラウザ固有のコードに安全です:

function SafeComponent() {
  const [screenWidth, setScreenWidth] = useState(0);
  
  useEffect(() => {
    // これはハイドレーション後にクライアント側でのみ実行されます
    setScreenWidth(window.innerWidth);
  }, []);
  
  // 一貫した初期レンダリングを返す
  if (screenWidth === 0) return <div>Loading...</div>;
  return <div>Screen width: {screenWidth}px</div>;
}

修正方法2: 動的インポートでSSRを無効化する

ブラウザAPIに大きく依存するコンポーネントの場合、サーバーレンダリングを完全にスキップします:

import dynamic from 'next/dynamic';

const ClientOnlyComponent = dynamic(
  () => import('./BrowserComponent'),
  { 
    ssr: false,
    loading: () => <div>Loading...</div> // レイアウトシフトを防ぐ
  }
);

export default function Page() {
  return <ClientOnlyComponent />;
}

修正方法3: 決定論的なレンダリングを確保する

レンダリング間で一貫性のある安定した値を生成します:

// ✅ propsから安定したIDを使用するか、一度だけ生成します
function StableComponent({ userId }) {
  // propsに基づいた決定論的なIDを使用
  const componentId = `user-${userId}`;
  
  return <div id={componentId}>Consistent content</div>;
}

// 真にランダムな値の場合は、サーバーサイドで生成
export async function getServerSideProps() {
  return {
    props: {
      sessionId: generateStableId() // サーバー上で一度生成
    }
  };
}

Next.js SSR問題のデバッグ

Next.js SSRのデバッグに取り組む際は、以下のテクニックを使用してください:

  1. 開発環境でReactの厳格なハイドレーション警告を有効にする
  2. ブラウザDevToolsを使用してサーバーとクライアントのHTMLを比較する
  3. console.logを追加してミスマッチを引き起こすコンポーネントを特定する
  4. React DevToolsを使用してコンポーネントツリーを検査する
// 一時的なデバッグヘルパー
useEffect(() => {
  console.log('Component hydrated:', typeof window !== 'undefined');
}, []);

将来のハイドレーションエラーを防ぐベストプラクティス

初期レンダリングを同一に保つ: サーバーとクライアントは、最初のレンダリングで同じHTMLを生成する必要があります。動的な更新はハイドレーション後に行ってください。

HTML構造を検証する: 適切なネストと有効なHTML要素を使用してください。W3C Validatorなどのツールを使用すると、問題を早期に発見できます。

JavaScriptを無効にしてテストする: サーバーレンダリングされたコンテンツは、JavaScriptなしでも機能する必要があり、堅牢なベースラインを確保します。

TypeScriptを使用する: 型チェックにより、実行時ではなく開発中に潜在的なミスマッチを検出できます。

結論

Next.jsにおけるReactハイドレーションミスマッチエラーは厄介ですが、パターンを理解すれば予測可能です。重要なのは、サーバーとクライアントが同一の初期HTMLを生成することを確実にすることです。クライアントサイドロジックに useEffect を使用する場合でも、動的インポートでSSRを無効化する場合でも、決定論的なレンダリングを確保する場合でも、解決策は常にこの原則に戻ります:最初のレンダリングを一貫させ、その後にクライアントサイド機能を追加してください。

覚えておいてください:コードがブラウザ環境に依存している場合は、初期レンダリングパスから除外してください。Next.jsアプリケーションはスムーズにハイドレートされ、ユーザーは舞台裏で起こっている複雑さを知ることはありません。

よくある質問

はい、できますが推奨されません。ハイドレーション警告は、UIの不整合を引き起こす可能性のある実際の問題を示しています。警告を抑制する代わりに、useEffectや動的インポートを使用して根本原因を修正し、適切な機能を確保してください。

日付は、タイムゾーンの違いによりサーバーとクライアントで異なるレンダリングをされることがよくあります。サーバー上で日付を文字列に変換するか、date-fnsのようなライブラリを使用して両方の環境で固定タイムゾーンを使用することで、一貫した形式を使用してください。

Next.jsのScriptコンポーネントをstrategy afterInteractiveまたはlazyOnloadで使用して、ハイドレーション後にサードパーティスクリプトを読み込んでください。これらのスクリプトに依存するコンポーネントの場合は、ssr falseで動的インポートにラップしてください。

SSRを無効化すると、それらのコンポーネントは初期HTMLに表示されなくなり、SEOに影響を与え、インタラクティブになるまでの時間が増加する可能性があります。真にクライアント依存の機能に対してのみ控えめに使用し、レイアウトシフトを防ぐためにローディング状態を提供してください。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay