NuxtJSとTypeScriptでnuxtServerInitのユースケースを解説!NuxtJSのライフサイクルを使いこなしましょう

NuxtJSとTypeScriptでnuxtServerInitのユースケースを解説!NuxtJSのライフサイクルを使いこなしましょう

こんにちは!

普段 NuxtJS で開発してる時に、処理を書いているのに実行されない…や middleware などの細かい実行順序がわからず苦労した方もいらっしゃるかと思います。

ライフサイクルを理解してると適切なフックに処理を書くことができ、予期しない不具合を防ぐことができます。

本記事では、「NuxtJS のライフサイクルのまとめ、nuxtServerInit のユースケース」をご紹介します!

はじめに

本記事では NuxtJS や TypeScript、Vuex の基本的な説明、Vue のライフサイクルの説明は行いません。

NuxtJS×TypeScript の環境構築については弊社のこちらの記事で紹介してますので、ぜひ参考にしてみてください!

https://www.ragate.co.jp/blog/articles/7080

想定する読者

  • NuxtJS で Vuex を使用して開発を行ってるヒト
  • TypeScript で開発を行っているヒト

NuxtJS のライフサイクルについて

nuxtServerInit のユースケースの前に、NuxtJS のライフサイクルについて説明したいと思います。

ライフサイクルを正しく理解していると、SSR や CSR、SSG などの各モードで適切な場所に処理を書くことができ、安全な開発ができます。

なお、今回は SSR のライフサイクルと各フックについて説明します。

https://ja.nuxtjs.org/docs/2.x/concepts/nuxt-lifecycle

初回アクセス時の実行順

ユーザーからのアクセス

serverMiddleware

外部サーバーを必要とせず に API などのルートを増やす事ができるものです。※/middleware の中に serverMiddleware の設定ファイルを追加すると、後述する middleware として認識されるため server-middleware 等のディレクトリを新たに作成する必要があります。

サーバーミドルウェアを middleware/ ディレクトリに追加しないでください。ミドルウェアは webpack によって本番バンドルにバンドルされ beforeRouteEnter で実行されます。serverMiddleware を middleware/ ディレクトリに追加すると、Nuxt によってミドルウェアとして誤って選択され、誤った依存関係がバンドルに追加されたり、エラーが発生したりします。

https://ja.nuxtjs.org/docs/2.x/configuration-glossary/configuration-servermiddleware

plugins(サーバーサイドのみ)

Vue.js プラグインなどの外部パッケージを簡単に追加できるものです。
/plugins 内で設定ファイルを記述し、 nuxt.config.js でパスを通す必要があります。
nuxt.config.js の plugins 内の mode: 'server' が指定されているもののみ実行されます。

nuxtServerInit(今回のメイン)

サーバーサイドで実行される Vuex アクションです。
非同期で store にデータをセットすることができます。また context が利用できます。
例:ログイン認証の結果をもとにページの振り分けなど
※ 定義できる場所は /store/index.ts のみなので注意

middleware

ページをレンダリング(生成)する前に実行させる関数やモジュールです。
下記の順番で実行されます。

  1. nuxt.config.js 内の modules
  2. /middleware 内のカスタム関数
  3. 各ページ内の middleware 関数

validate

動的なコンポーネントの値のバリデーションが行われます。

asyncData or fetch

外部の API から axios などでデータを取得し、サーバーサイドでコンテンツを生成できるものです。
どちらの場合もページが読み込まれるたびに実行されます。

beforeCreated(Vue)

Vue インスタンスが生成され、初期化されるときと同期的に実行されます
※ Create の前ではなく同期的な点に注意

created(Vue)

Vue インスタンスが生成され、初期化されたに実行されます

fetch

NuxtJs の2.12.0以降の fetch では、Vue インスタンスが生成された後に実行できるようになりました。
ここで this が使えるようになります。

Nuxt.js v2.12 から、すべての Vue コンポーネントに対して fetch という新しいフックが導入されています。非同期データを取得する必要があるたびに fetch を使います。fetch はサーバーサイドではルートをレンダリングするときに、クライアントサイドでは遷移するときに呼び出されます。

https://ja.nuxtjs.org/docs/2.x/components-glossary/pages-fetch

Vue のライフサイクルへ…

※ ページ内の <nuxt-link to=””></nuxt-link> を介して別ページに遷移した場合は middleware から再度実行されます。

NuxtJS の SSR のライフサイクルを理解したところで、
nuxtServerInit の具体的な使い方について説明します。

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 での開発はお気軽にお問い合わせください。