こんにちは!
普段 NuxtJS で開発してる時に、処理を書いているのに実行されない…や middleware などの細かい実行順序がわからず苦労した方もいらっしゃるかと思います。
ライフサイクルを理解してると適切なフックに処理を書くことができ、予期しない不具合を防ぐことができます。
本記事では、「NuxtJS のライフサイクルのまとめ、nuxtServerInit のユースケース」をご紹介します!
本記事では NuxtJS や TypeScript、Vuex の基本的な説明、Vue のライフサイクルの説明は行いません。
NuxtJS×TypeScript の環境構築については弊社のこちらの記事で紹介してますので、ぜひ参考にしてみてください!
https://www.ragate.co.jp/blog/articles/7080
nuxtServerInit のユースケースの前に、NuxtJS のライフサイクルについて説明したいと思います。
ライフサイクルを正しく理解していると、SSR や CSR、SSG などの各モードで適切な場所に処理を書くことができ、安全な開発ができます。
なお、今回は SSR のライフサイクルと各フックについて説明します。
外部サーバーを必要とせず に API などのルートを増やす事ができるものです。※/middleware
の中に serverMiddleware の設定ファイルを追加すると、後述する middleware として認識されるため server-middleware
等のディレクトリを新たに作成する必要があります。
サーバーミドルウェアを middleware/ ディレクトリに追加しないでください。ミドルウェアは webpack によって本番バンドルにバンドルされ beforeRouteEnter で実行されます。serverMiddleware を middleware/ ディレクトリに追加すると、Nuxt によってミドルウェアとして誤って選択され、誤った依存関係がバンドルに追加されたり、エラーが発生したりします。
https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-servermiddleware
Vue.js プラグインなどの外部パッケージを簡単に追加できるものです。/plugins
内で設定ファイルを記述し、 nuxt.config.js
でパスを通す必要があります。
※ nuxt.config.js
の plugins 内の mode: 'server'
が指定されているもののみ実行されます。
サーバーサイドで実行される Vuex アクションです。
非同期で store にデータをセットすることができます。また context が利用できます。
例:ログイン認証の結果をもとにページの振り分けなど
※ 定義できる場所は /store/index.ts
のみなので注意
ページをレンダリング(生成)する前に実行させる関数やモジュールです。
下記の順番で実行されます。
nuxt.config.js
内の modules/middleware
内のカスタム関数動的なコンポーネントの値のバリデーションが行われます。
外部の API から axios などでデータを取得し、サーバーサイドでコンテンツを生成できるものです。
どちらの場合もページが読み込まれるたびに実行されます。
Vue インスタンスが生成され、初期化されるときと同期的に実行されます
※ Create の前ではなく同期的な点に注意
Vue インスタンスが生成され、初期化された後に実行されます
NuxtJs の2.12.0以降の fetch では、Vue インスタンスが生成された後に実行できるようになりました。
ここで this
が使えるようになります。
Nuxt.js
https://ja.nuxtjs.org/docs/2.x/components-glossary/pages-fetchv2.12
から、すべての Vue コンポーネントに対してfetch
という新しいフックが導入されています。非同期データを取得する必要があるたびに fetch を使います。fetch
はサーバーサイドではルートをレンダリングするときに、クライアントサイドでは遷移するときに呼び出されます。
※ ページ内の <nuxt-link to=””></nuxt-link> を介して別ページに遷移した場合は middleware から再度実行されます。
NuxtJS の SSR のライフサイクルを理解したところで、
nuxtServerInit の具体的な使い方について説明します。
nuxtServerInit はサーバーサイドからデータを直接クライアントサイドへ渡す際に便利なフックです。
ブログやメディアサイトを実装する際に、サイドメニューによくある人気記事や月別の記事などは store に格納する事が多いです。
store に格納する際に、nuxtServeInit のフック内に記事取得処理を記述することで、ページ遷移のたびに記事取得の処理を行わずに済みます。
これは API のコール数の削減や読み込み時間の短縮など、パフォーマンスの面で効果を発揮します。
処理の記述場所は /store/index.ts
です。まずは NuxtJS プロジェクトで Vuex を利用できるようにします。
公式サイトでは vuex-module-decorators
が推奨されているため、こちらをインストールします。
npm install -D vuex-module-decorators
or
yarn add -D vuex-module-decorator
store モジュールで API 通信を行いたいので、axios を使えるようにします。plugins で axios を使えるようにするため export します。
import { NuxtAxiosInstance } from '@nuxtjs/axios'
// eslint-disable-next-line import/no-mutable-exports
let $axios: NuxtAxiosInstance
export function initializeAxios(axiosInstance: NuxtAxiosInstance) {
$axios = axiosInstance
}
export { $axios }
plugins で $axios を読み込んで ts ファイルで使えるようにします。
import { Plugin } from '@nuxt/types'
import { initializeAxios } from '~/utils/api'
const accessor: Plugin = ({ $axios }) => {
initializeAxios($axios)
}
export default accessor
...
plugins: ['~/plugins/axios-accessor'],
...
次に store を作成します。今回は記事を格納する store のサブモジュールを作成します。
type
については types
フォルダで管理することが多いですが、今回は同一ファイルに記述します。
import { Module, VuexModule, Action, Mutation } from 'vuex-module-decorators'
import { $axios } from '~/utils/api'
type Post = {
userId: number
id: number
title: String
body: String
}
@Module({
name: 'posts',
stateFactory: true,
namespaced: true,
})
export default class PostModule extends VuexModule {
private posts: Post[] = []
public get getPosts() {
return this.posts
}
@Mutation
private setPosts(posts: Post[]) {
this.posts = posts
}
@Action
public async fetchPosts() {
const POST_API =
'https://jsonplaceholder.typicode.com/posts?_start=0&_limit=5'
const { data } = await $axios.get<Post[]>(POST_API)
this.setPosts(data)
}
}
※今回はダミー API として jsonplaceholder を利用しております。
import { ActionTree, Store } from 'vuex'
import { ActionContext } from 'vuex/types'
import { initialiseStores } from '~/utils/store-accessor'
export const state = () => ({})
export type RootState = ReturnType<typeof state>
const initializer = (store: Store<any>) => initialiseStores(store)
export const actions: ActionTree<any, any> = {
nuxtServerInit: async (context: ActionContext<RootState, RootState>) => {
// PostsModuleのfetchPostsを実行する
await context.dispatch('posts/fetchPosts')
},
}
export const plugins = [initializer]
export * from '~/utils/store-accessor'
nuxtServerInit はプライマリモジュールでのみ実行可能で、サブモジュール内に記述しても実行されないので注意が必要です。
このアクションは受け取るのは(store/index.js 内の)プライマリモジュールだけです。ここから他のモジュールのアクションに呼び出しを繋いでいく必要があります。
https://ja.nuxtjs.org/docs/2.x/directory-structure/store#nuxtserverinit-%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
作成した postsStore をコンポーネントで読み込めるように export します。
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import Posts from '~/store/posts'
// eslint-disable-next-line import/no-mutable-exports
let postsStore: Posts
function initialiseStores(store: Store<any>): void {
postsStore = getModule(Posts, store)
}
export { initialiseStores, postsStore }
※ eslint でエラーが出る場合は上記のようにコメントで除外させるようにします。
実際にコンポーネントから呼び出す際は、
store モジュールを読み込み、getter で store モジュールの getter を呼ぶことで state を呼び出すことができます。
<template>
<ul>
<li v-for="(post, index) in getPosts" :key="index">
<p>{{ post.id }}</p>
<p>{{ post.title }}</p>
</li>
</ul>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
// storeモジュール読み込み
import { postsStore } from '~/store'
@Component
export default class PostsComponent extends Vue {
// postsStoreに格納されているstateを取得
get getPosts() {
return postsStore.getPosts
}
}
</script>
上記の実装で初回読み込み時のみに処理が走り、記事を取得できるようになりました。
今回はブログやメディアサイトでの nuxtServerInit のユースケースについて紹介しましたが、認証処理などでも使われるケースが多いです。
複雑な実装になればなるほどライフサイクルの理解が重要になってきます。
認証などの複雑な実装や今回のようなパフォーマンス改善で nuxtServerInit は大変有用ですのでぜひ参考にしてみてください!
フロントエンドの NuxtJS × TypeScript での開発はお気軽にお問い合わせください。
スモールスタート開発支援、サーバーレス・NoSQLのことなら
ラーゲイトまでご相談ください
低コスト、サーバーレスの
モダナイズ開発をご検討なら
下請け対応可能
Sler企業様からの依頼も歓迎