VueのHydrationが失敗したメモ
NuxtのProduction Build後のリソースでVue Hydrationがエラーになった
この辺回避しておけば防げるかも
エラーの内容
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
}
関連しそうなIssue
Issueの内容的にはVue Hydrationが失敗するようなケースはCoreチームが対処できるものではない。
Vue3では改善できるようにしているみたいな感じ?
参考リンクとして次のブログが記載されていた。
次のコードで開発環境とビルド後の環境で挙動に差が出た
<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が文法違反ではないかを確認する必要があるかも