Open12

VueのHydrationが失敗したメモ

NuxtのProduction Build後のリソースでVue Hydrationがエラーになった

この辺回避しておけば防げるかも

https://zenn.dev/lollipop_onl/scraps/f6495b181135ca#comment-25e598278a2421

エラーの内容

DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
    at Object.appendChild (https://my.domain/_nuxt/vendors/commons.3cf3456.js:5196:8)

function removeChild (node, child) {
  node.removeChild(child);
}

function appendChild (node, child) {
  node.appendChild(child);
//^ Error happen here
}

function parentNode (node) {
  return node.parentNode
}

次のコードで開発環境とビルド後の環境で挙動に差が出た

<template>
  <div>
    <h1>Title.</h1>
    <div>name {{ myName }}</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      myName: 'simochee',
    };
  },
  fetch() {
    this.$nuxt.error({ statusCode: 404 });
  },
}
</script>
# 開発環境
> Mismatching childNodes vs. VNodes:  NodeList(3) [h1, text, div] [VNode]

> [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.
# ビルド後
> DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.
    at Object.appendChild 

fetch() メソッドの挙動が怪しい?

次のコードでも同様に警告とエラーが出た
beforeMountで存在しない要素(v-ifがfalse)を追加しようとするとエラーになる

<template>
  <div>
    <h1>Title.</h1>
    <div v-if="isVisible">表示するか</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: false,
    };
  },
  beforeMount() {
    this.isVisible = true;
  },
}
</script>

次のコードは開発環境では警告が表示されるが、ビルド後はエラーが発生しない
表示されている要素を消す分には問題ない模様

<template>
  <div>
    <h1>Title.</h1>
    <div v-if="isVisible">表示するか</div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: true,
    };
  },
  beforeMount() {
    this.isVisible = false;
  },
}
</script>

考察:
コンポーネントがmountする前に存在しない要素を追加しようとするとエラーになりうる
ともかく、開発環境でSSRとCSRのコンフリクトが発生しないよう調整すればHydrationのエラーは発生しなさそう
あと、beforeMountでDataを更新するのは避けたほうがいいかも

ビルド後エラーが発生した事例:

  • beforeMountでDataを更新したらv-if=trueとなる場合
  • fetch() { this.$nuxt.error({ statusCode: 404 }) } のページでSSRした場合
    • statusCodeを指定していなければエラーにならない

fetch() 内でthis.$nuxt.errorを使用する場合、Promiseで待機させればエラーにならなくなる

<template>
  <div>
    <h1>Title.</h1>
  </div>
</template>

<script>
const wait = new Promise((resolve) => setTimeout(resolve, 1000));

export default {
  async fetch() {
    await wait();
    
    this.$nuxt.error({ statusCode: 404 });
  },
}
</script>

fetch()のContextから参照したerror()を使用してもエラーにならなかった

<template>
  <div>
    <h1>Title.</h1>
  </div>
</template>

<script>
export default {
  fetch({ error }) {
    error({ statusCode: 500 });
  },
}
</script>

ビルド後エラーが発生した事例(アップデート):

  • beforeMountでDataを更新したらv-if=trueとなる場合
  • fetch() 内で同期的に this.$nuxt.error({ statusCode: number }) を呼んだとき
    • 非同期(遅延済み)でthis.$nuxt.error({ statusCode: number }) を呼んでもエラーにならない
    • Contextのerror()を呼んでもエラーにならない

ビルド後エラーが発生した事例(アップデート):

  • p タグの中に table タグが入っていた(文法違反)
  • button タグの中に button タグが入っていた(文法違反)

エラー時に報告される構造を比較すると、SSRされたHTMLを解析したときには文法違反している要素が読み込まれないような挙動になっていた
当該箇所で v-if を使っていなくても、HTMLが文法違反ではないかを確認する必要があるかも

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