Nuxt.jsというかSSR関係を触る際にまず疑問になるのは「どこまでがサーバーサイドレンダリング (SSR) でどこからがクライアントサイドレンダリング (CSR) なの?」ということだと思います。
基本
簡単に言えば、ブラウザでURL直接指定で開けばSSR、それ以降はCSR、ということになります。
当たり前ですが、サーバーサイドレンダリングはサーバーでの実行なので、windowオブジェクトも存在しないし、そこから外部サーバー(APIサーバーなど)にアクセスする際はそこからのアクセスになります。そのままではブラウザのcookieも利用できません。
axios-moduleは何をしているのか
上記の通り、SSRになった場合とCSRになった場合は通信経路も変わるしcookieの取扱も変わります。
これらを真面目に対応すると結構大変なのですが、 axios-module を使うと大体のことを解決してくれます。
axios-moduleは大きく以下のようなことを行ってくれます。
他にもいろいろありますが、重要なのはこの2点で、これにより通信時のレンダリング場所がサーバーかクライアントかを意識せずにAPIアクセス処理を書くことができるようになります。Nuxtで開発する場合は必須と言っても過言ではないでしょう。
なお、複数のエンドポイントを指定したりはできないので、API通信先は1箇所に固定されることになります。複数のエンドポイントが発生しそうな場合はクライアント(Nuxt)だけで解決せず、BFF (Backends For Frontends) パターンを用いて処理するのが良いでしょう。
ライフサイクル
Nuxtを使うとコンポーネントにメソッドが増えたり、ミドルウェアやプラグインのような仕組みが追加されます。これらがSSRの場合とCSRの場合でどのように呼ばれるかは使ってみないとわからないところです。
そこで、サンプルプロジェクト を用意し、調査してみました。
初回アクセス (SSR)
- サーバーサイド:プラグイン→
nuxtServerInit
→共通ミドルウェア→ページミドルウェア(定義順)→ページのvalidate
→ページのasyncData
→ページのfetch
→ページのcreated
→コンポーネントのcreated
- クライアントサイド:プラグイン→ページの
created
→コンポーネントのcreated
→ コンポーネントのmounted
→ページのmounted
そこからの遷移 (CSR)
- クライアントサイド:共通ミドルウェア→ページミドルウェア(定義順)→ページの
validate
→ページのasyncData
→ページのfetch
→ページのcreated
→コンポーネントのcreated
→ コンポーネントのmounted
→ページのmounted
つまりどういうことか
Nuxtでアプリを作る際は、SSRでもCSRでも問題なく表示できるように作る必要があります。また、クローラーなどからのアクセスは常にSSRになるため、クローラーなどに拾ってほしい情報はSSRの段階で確定させる必要があります。
一方で、windowオブジェクトにアクセスするもの、例えばlocalStorageを使うようなものはクライアントサイドのみでしか実行することはできません。 mounted
以外で書く場合は process.server
変数などを使って分岐させる必要があります。
特に注意したいのが created
で( beforeCreate
も)、SSR時とCSR時の両方で呼ばれます。普通に書くと二重に処理してしまうことになるので、意識して書かないと意図しない挙動になる可能性があります。
まとめ
このあたりの挙動は分散して書かれてはいるものの、1ページにまとまっているものが見つからなかったため、今回用意してみました。
SSRは便利ではあるものの、どうしても層が1つ増えるために挙動が複雑になります。ちゃんと把握していれば怖くないので、どこで何が動いているのかを意識しながら書くようにしましょう。