Vue.jsのSSRではcreatedが2回実行される!?
In SSR, Vue.js is created twice!!??
Nuxt.jsなどのフレームワークを使えば、Vue.jsを用いたSSR(サーバーサイドレンダリング)のアプリケーションを簡単に作成することができます。ただし、どこまでがサーバーで実行され、どこからクライアントで実行されるのかを正しく理解しておかないと、期待通りのSSRにはなりません。タイトルでネタバレしていますが、今回はここを少しだけ掘り下げてみたいと思います。
Vue.jsのcreated、mounted、data
Nuxt.jsの話の前に、そもそもVue.jsのライフサイクルフックとして用意されている created
と mounted
、およびデータをリアクティブにする data
プロパティについて簡単に触れます。
created
は、Vueインスタンスが作成された直後に呼ばれる関数で、Vueインスタンスの各種プロパティ、メソッドは利用できますが、レンダリングが完了していないため、エレメントは利用できません。
mounted
は、レンダリングが完了した直後に呼ばれる関数で、エレメントへのアクセスも可能となります。なお、子コンポーネントのレンダリング完了は保証されていません。
data
は、ライフサイクルフックではなく、Vue.jsの基本となるデータバインディングの対象となるデータを定義するプロパティです。単純なオブジェクトとしても定義可能ですが、コンポーネントの場合は関数として定義する必要があります。
この他にもいくつかライフサイクルフックやプロパティは用意されています。ライフサイクルについては公式ドキュメントに図が載っているので、そちらも参照してください。
Nuxt.jsのasyncData、fetch
Nuxt.jsはVue.jsをラップしたフレームワークで、SSRなアプリケーションを簡単に作成することができます。このNuxt.jsが独自に提供しているフック関数として、 asyncData
と fetch
があるので、こちらについても先に紹介しておきます。
asyncData
は、ページがロードされる前(Vueインスタンスが作成される前)に呼び出される関数で、通常API呼び出しなどの非同期処理を記述します。結果はVueインスタンスの data
とマージされ、同じキーが存在する場合は asyncData
の結果で上書きされます。
fetch
は asyncData
と同様、ページがロードされる前に呼び出され、非同期処理を記述するための関数です。 asyncData
との違いは、結果をVueインスタンスの data
とマージしないという点です。
Nuxt.jsが用意する関数についても、上記以外にいくつかありますが、今回はこのふたつに絞って挙動を確認します。Nuxt.jsの他の関数との関係などは、公式ドキュメントを参照してください。
実行される順番と環境を確認
前置きが長くなりましたが、ここまでで紹介したVue.jsやNuxt.jsの関数について、どの順番で、どの環境(サーバーかクライアントか)で実行されるのか、実際に動かして確認したいと思います。
用意したのは、下記のようなコードです。
<template>
<div>
{{ value }}
</div>
</template><script>
export default {
data() {
printLog('data ')
return {
value: 'data'
}
},
asyncData() {
printLog('asyncData')
return {
value: 'asyncData'
}
},
fetch() {
printLog('fetch ')
},
created() {
printLog('created ')
this.value = 'created'
},
mounted() {
printLog('mounted ')
this.value = 'mounted'
}
}function printLog(funcName) {0
console.log(funcName + ': client=' + process.client + ', server=' + process.server)
}
</script>
まずは他のページから nuxt-link
で遷移してきた場合の、ブラウザのディベロッパーツールで確認できるコンソールログです。SPA(シングルページアプリケーション)モードとなるため、全てクライアントサイドで実行されていることがわかります。
続いて、このページに直接遷移してきた(SSRの)場合のログです。 asyncData
、 fetch
、 data
、 created
がサーバーサイドで実行されていることがわかります。注目したいのは、 data
、 created
のふたつについては、クライアントサイドでも実行されているという点です。
ちなみに画面にしれっと表示している value
の値は、SSRでもSPAでもブラウザ上は mounted
となります。ただ、curl等で取得すると created
となった状態でhtmlが返ってきます。
ユニバーサルなコードを書くために
SSRでもSPAでも、どちらでも正しく動くユニバーサルなコードを書くためには、どの処理をどこに書くのが正しいのかを理解しておく必要があります。
上述の挙動を考慮すると、環境依存のない処理は、一度しか実行されないことを保証してくれる asyncData
や fetch
に記述するのがセオリーですが、 localStorage
やブラウザに保存された認証情報を利用するサードパーティAPIのようなクライアントサイドでしか実行できない処理は、必ずクライアントで実行される created
や mounted
で記述する必要があります。なお、 created
はサーバーサイドでも実行されるため、サーバーサイドで実行するとエラーとなるものは、 process.client
などを用いて適切に分岐を入れる必要があります。
実はこの記事を書く前は、SSRはNuxt.jsが提供している機能だと思っていましたが、大元は vue-server-render
が提供しているのですね。だからVue.jsのドキュメントにもSSRについての記載があります。困ったらまずは公式ドキュメントに立ち戻るのが、やっぱり早道ですね。