📚

VueのSSR時におけるハイドレーションエラー対処法

2 min read

参考記事
What to do when Vue hydration fails
Client Side Hydration


(Vue)ハイドレーションとは?

ハイドレーションは、Vueがサーバー側でレンダリングされたマークアップを変換し、それをリアクティブにして、Vueからの動的な変更を反映できるようにするプロセス。

Vueが期待しているマークアップと、実際にレンダリングされたHTMLが異なるとハイドレーションエラーとなる。


Vueで開発中に以下のようなWarningに出会す時があります。

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content.
 This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>,
 or missing <tbody>. Bailing hydration and performing full client-side render.

Warningの内容↓

クライアント側でレンダリングされた仮想DOMツリーがサーバーでレンダリングされたものと一致していません

このWarningは開発環境でしか表示されません。
本番環境ではエラーとして出力されたり、レンダリングが完了しなかったりします。
なのでこの問題は解決しておきべきです。

今回はこのエラーの発生箇所を特定する方法についてメモしておきます。

ちなみに、すでに上記Warningの二つ上あたりに、以下ログが出ていたらそこが発生箇所です。

Parent: <~~>...</~~>

ただ、これが人によっては表示されなかったりするので別の特定方法を提示します。
また発生原因も。

発生元を特定

Warningの詳細を開くと以下のようになっています。
ここで hydrateの呼び出し元をクリックします。
画像で言うと hydrate @ vue.runtime.esm.js?2b0e:6378 のところです。

するとVueのハイドレーション機能のソースコードが展開され、
展開された行の少し上あたりにこのようなコードがあります。

if (process.env.NODE_ENV !== 'production') {
  if (!assertNodeMatch(elm, vnode, inVPre)) {
    return false
  }
}

ここの6374行目の部分にデバッガーを設定します。(6374のところをクリック)

return false

そしてこの状態で画面をリロードすると、レンダリングが途中で止まります。

あとはDevToolのConsoleタブやElmentsタブから、
ハイドレーションに失敗したDOM要素を特定していきます。
(最終的に表示されるDOM要素と異なるのでそこで特定できます。)

発生原因/修正方法等

考えられる原因としては以下。

1. サーバ側とクライアント側の状態が異なる

この原因が一番多いそうで、
この場合の解決策としては、データの取得方法やタイミングを修正するなどです。

2. 無効なHTMLを記述している

無効なHTMLを記述していないか確認。

例えば以下のようなHTMLを記述した場合、
正しくはtableタグの中にtbodyタグがないといけません。

[不正なHTML]

<table>
  <tr><td>hoge</td></tr>
</table>

[有効なHTML]

<table>
  <tbody>
    <tr><td>hoge</td></tr>
  </tbody>
</table>

しかしブラウザではこの不正なHTMLに自動的にtbodyタグを挿入してくれます。
そのため、DOMツリーに不整合が生じます。

3. 最終手段

client-onlyタグでラップし、サーバー側でのレンダリングを避けます。


<client-only></client-only>

※余談

開発環境では、
クライアントとサーバーでレンダリングされたDOMツリーが一致していなくても、
既存のDOMを破棄して最初からレンダリングし直してくれています。
しかし本番環境では、
パファーマンスを最大化するためにこの機能は無効になっています。

また、ハイドレーションは最初にページがサーバーによってレンダリングされた時(初回リクエスト時)にのみ発生します。

Discussion

ログインするとコメントできます