Vue.jsのSSRではcreatedが2回実行される!?

In SSR, Vue.js is created twice!!??

Takuo
VELTRA Engineering
6 min readAug 20, 2019

--

Nuxt.jsなどのフレームワークを使えば、Vue.jsを用いたSSR(サーバーサイドレンダリング)のアプリケーションを簡単に作成することができます。ただし、どこまでがサーバーで実行され、どこからクライアントで実行されるのかを正しく理解しておかないと、期待通りのSSRにはなりません。タイトルでネタバレしていますが、今回はここを少しだけ掘り下げてみたいと思います。

Vue.jsのcreated、mounted、data

Nuxt.jsの話の前に、そもそもVue.jsのライフサイクルフックとして用意されている createdmounted 、およびデータをリアクティブにする dataプロパティについて簡単に触れます。

created は、Vueインスタンスが作成された直後に呼ばれる関数で、Vueインスタンスの各種プロパティ、メソッドは利用できますが、レンダリングが完了していないため、エレメントは利用できません。

mounted は、レンダリングが完了した直後に呼ばれる関数で、エレメントへのアクセスも可能となります。なお、子コンポーネントのレンダリング完了は保証されていません。

data は、ライフサイクルフックではなく、Vue.jsの基本となるデータバインディングの対象となるデータを定義するプロパティです。単純なオブジェクトとしても定義可能ですが、コンポーネントの場合は関数として定義する必要があります。

この他にもいくつかライフサイクルフックやプロパティは用意されています。ライフサイクルについては公式ドキュメントに図が載っているので、そちらも参照してください。

Nuxt.jsのasyncData、fetch

Nuxt.jsはVue.jsをラップしたフレームワークで、SSRなアプリケーションを簡単に作成することができます。このNuxt.jsが独自に提供しているフック関数として、 asyncDatafetch があるので、こちらについても先に紹介しておきます。

asyncData は、ページがロードされる前(Vueインスタンスが作成される前)に呼び出される関数で、通常API呼び出しなどの非同期処理を記述します。結果はVueインスタンスの data とマージされ、同じキーが存在する場合は asyncData の結果で上書きされます。

fetchasyncData と同様、ページがロードされる前に呼び出され、非同期処理を記述するための関数です。 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の)場合のログです。 asyncDatafetchdatacreated がサーバーサイドで実行されていることがわかります。注目したいのは、 datacreated のふたつについては、クライアントサイドでも実行されているという点です。

ちなみに画面にしれっと表示している value の値は、SSRでもSPAでもブラウザ上は mounted となります。ただ、curl等で取得すると created となった状態でhtmlが返ってきます。

ユニバーサルなコードを書くために

SSRでもSPAでも、どちらでも正しく動くユニバーサルなコードを書くためには、どの処理をどこに書くのが正しいのかを理解しておく必要があります。

上述の挙動を考慮すると、環境依存のない処理は、一度しか実行されないことを保証してくれる asyncDatafetch に記述するのがセオリーですが、 localStorage やブラウザに保存された認証情報を利用するサードパーティAPIのようなクライアントサイドでしか実行できない処理は、必ずクライアントで実行される createdmounted で記述する必要があります。なお、 created はサーバーサイドでも実行されるため、サーバーサイドで実行するとエラーとなるものは、 process.client などを用いて適切に分岐を入れる必要があります。

実はこの記事を書く前は、SSRはNuxt.jsが提供している機能だと思っていましたが、大元は vue-server-render が提供しているのですね。だからVue.jsのドキュメントにもSSRについての記載があります。困ったらまずは公式ドキュメントに立ち戻るのが、やっぱり早道ですね。

--

--

Takuo
VELTRA Engineering

Engineer who likes travel, simple code, and something new