Nuxt とハイドレーション

ハイドレーションの問題を修正することが重要な理由

開発中、ハイドレーションの問題に直面することがあります。これらの警告を無視しないでください。

なぜそれらを修正することが重要なのでしょうか?

ハイドレーションの不一致は単なる警告ではありません。それはアプリケーションを壊す可能性のある深刻な問題の指標です。

パフォーマンスへの影響

  • インタラクティブになるまでの時間の増加:ハイドレーションエラーはVueにコンポーネントツリー全体を再レンダリングさせ、Nuxtアプリがインタラクティブになるまでの時間を増加させます。
  • 劣悪なユーザーエクスペリエンス:ユーザーはコンテンツの点滅や予期せぬレイアウトのずれを目にすることがあります。

機能の問題

  • インタラクティブ性の破損:イベントリスナーが適切にアタッチされず、ボタンやフォームが機能しなくなる可能性があります。
  • 状態の不整合:アプリケーションの状態が、ユーザーが見ているものとアプリケーションがレンダリングされていると考えているものの間で同期が取れなくなる可能性があります。
  • SEOの問題:検索エンジンが、ユーザーが実際に目にするコンテンツとは異なるコンテンツをインデックスする可能性があります。

検出方法

開発コンソールの警告

開発中、Vueはブラウザのコンソールにハイドレーションの不一致に関する警告をログに記録します。

よくある原因

サーバーコンテキストでのブラウザ専用API

問題:サーバーサイドレンダリング中にブラウザ固有のAPIを使用すること。

<template>
  <div>User preference: {{ userTheme }}</div>
</template>

<script setup>
// This will cause hydration mismatch!
// localStorage doesn't exist on the server!
const userTheme = localStorage.getItem('theme') || 'light'
</script>

解決策useCookieを使用できます。

<template>
  <div>User preference: {{ userTheme }}</div>
</template>

<script setup>
// This works on both server and client
const userTheme = useCookie('theme', { default: () => 'light' })
</script>

不整合なデータ

問題:サーバーとクライアント間でデータが異なること。

<template>
  <div>{{ Math.random() }}</div>
</template>

解決策:SSRフレンドリーな状態を使用する

<template>
  <div>{{ state }}</div>
</template>

<script setup>
const state = useState('random', () => Math.random())
</script>

クライアント状態に基づく条件付きレンダリング

問題:SSR中にクライアント専用の条件を使用すること。

<template>
  <div v-if="window?.innerWidth > 768">
    Desktop content
  </div>
</template>

解決策:メディアクエリを使用するか、クライアントサイドで処理する

<template>
  <div class="responsive-content">
    <div class="hidden md:block">Desktop content</div>
    <div class="md:hidden">Mobile content</div>
  </div>
</template>

副作用のあるサードパーティライブラリ

問題:DOMを変更したり、ブラウザ依存関係を持つライブラリ(これはタグマネージャーで非常によく起こります)。

<script setup>
if (import.meta.client) {
    const { default: SomeBrowserLibrary } = await import('browser-only-lib')
    SomeBrowserLibrary.init()
}
</script>

解決策:ハイドレーションが完了した後にライブラリを初期化する

<script setup>
onMounted(async () => {
  const { default: SomeBrowserLibrary } = await import('browser-only-lib')
  SomeBrowserLibrary.init()
})
</script>

時間に基づいた動的なコンテンツ

問題:現在の時間に基づいて変化するコンテンツ。

<template>
  <div>{{ greeting }}</div>
</template>

<script setup>
const hour = new Date().getHours()
const greeting = hour < 12 ? 'Good morning' : 'Good afternoon'
</script>

解決策NuxtTimeコンポーネントを使用するか、クライアントサイドで処理する

<template>
  <div>
    <NuxtTime :date="new Date()" format="HH:mm" />
  </div>
</template>
<template>
  <div>
    <ClientOnly>
      {{ greeting }}
      <template #fallback>
        Hello!
      </template>
    </ClientOnly>
  </div>
</template>

<script setup>
const greeting = ref('Hello!')

onMounted(() => {
  const hour = new Date().getHours()
  greeting.value = hour < 12 ? 'Good morning' : 'Good afternoon'
})
</script>

まとめ

  1. SSRフレンドリーなコンポーザブルを使用するuseFetchuseAsyncDatauseState
  2. クライアント専用コードをラップする:ブラウザ固有のコンテンツにはClientOnlyコンポーネントを使用する
  3. 一貫したデータソース:サーバーとクライアントが同じデータを使用していることを確認する
  4. セットアップでの副作用を避ける:ブラウザ依存のコードをonMountedに移動する
ハイドレーションについてよりよく理解するために、VueのSSRハイドレーションの不一致に関するドキュメントを読むことができます。