Nuxt Hydration Fails – SSR與CSR的比對錯誤

在 Nuxt 中,當我們 refresh 頁面時,寫在 data 或 computed 中的程式碼,在 server 端與 client 端都會各執行一次,並且在前端會進行html tag的比對,若兩者不相符會噴錯,甚至沒有辦法進行接下來的操作。

因此,當我們用了 new Date().getTime(), Math.random() 這類的程式碼在data中,是一定會造成錯誤的。甚至 new Date().getDay() 也是有可能,因為 server time 有很大的可能性,跟 client 端的時區不同。最後我們再來看看如何解決,我們先來看看相同的程式碼,在 Nuxt2 與 Nuxt3 分別會導出怎樣的結果。

建立測試頁面

我們先分別開 Nuxt2 與 Nuxt3 兩個環境,分別給 Nuxt2 & Nuxt3 建立一個 Option API 的頁面,原始碼如下:

<template>
  <div>
    Test Render Error
    <hr>
    <div v-if="isMonday">
      isMonday: {{ isMonday }}
      <hr>
    </div>

    <button
      type="button"
      class="btn"
      @click="doSomething()"
    >
      doSomething()
    </button>
  </div>
</template>

<script>
export default {
  data () {
    const isMonday = process.server
      ? new Date(new Date().getTime() - 18 * 60 * 60 * 1000).getDay() === 1
      : new Date().getDay() === 1

    console.log(isMonday)

    return {
      isMonday
    }
  },

  methods: {
    doSomething () {
      console.log('doSomething()')
    }
  }
}
</script>

isMonday 的部分為了在 server 模擬不同時區,給他 new Date(new Date().getTime() – 18 * 60 * 60 * 1000).getDay() === 1,而 client 端就是 new Date().getDay() === 1,這部分各位在測試時,可以依照時間做調整。

另外我們在,Nuxt3環境中,另外建立一頁 Composition API 的頁面,用於測試 Composition API 是否會得到不同的結果,原始碼如下:

<template>
  <div>
    Test Render Error
    <hr>
    <div v-if="isMonday">
      isMonday: {{ isMonday }}
      <hr>
    </div>

    <button
      type="button"
      class="btn"
      @click="doSomething()"
    >
      doSomething()
    </button>
  </div>
</template>

<script setup>
const isMonday = process.server
  ? new Date(new Date().getTime() - 18 * 60 * 60 * 1000).getDay() === 1
  : new Date().getDay() === 1

console.log(isMonday)

function doSomething () {
  console.log('doSomething()')
}
</script>

測試結果 Nuxt2 與 Nuxt3 結果大不同

在 Nuxt2 的開發環境中,若發生render錯誤,會出現類似下圖的錯誤訊息,但 method 還是可被執行

而在正式環境中,情況就比較不妙。當發生錯誤時,會出現下圖的錯誤訊息,雖然 NuxtLink 還可以正常換頁,但 call method 時卻毫無反應。假設我們的頁面主要功能都是以 methods 做反饋,一但 render 錯誤,這一頁就會死掉。也就是說,在測試環境中若沒注意到錯誤訊息、沒有解決,在正式環境中會導致使用者無法操作。

而在 Nuxt3 中,無論是用 composition API、option API ,無論是正式或是測試環境,都可以正常運作,僅僅在測試環境會噴警告而已,如下圖:

下圖是 Nuxt3 正式環境,一樣可以執行 doSomething()

下圖是 Nuxt3 測試環境,我們可以看到,即使 isMonday 與前台不同,給了 false,還是沒有造成前台會無法繼續操作的嚴重錯誤。

結論

目前看起來,這種情況的 render 錯誤,需要在 Nuxt2 上多加注意,一個不小心可能會導致使用者無法操作的這種低級錯誤,而在 Nuxt3 上目前沒有看得出來的影響。

Nuxt2的解決方式

如前述,在 Nuxt 中,當我們 refresh 頁面時,寫在 data 或 computed 中的程式碼,在 server 端與 client 端都會各執行一次。因此,我們可以用 asyncData 來設定有用到 Math.random(), new Date().getTime() 這類的資料,這樣 refresh 時就不會在 client side render 時重複跑一次。當然,在只有 client sider render 的換頁情況下也不會有問題。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料