Help us understand the problem. What is going on with this article?

Nuxt.jsでVue.jsのSSRのWebアプリケーションの制作についてまとめ

はじめに

この記事はLinkbal Advent Calendar 2019の3日目の記事です。
今年は弊社のサービスのリニュアルでVue.js初心者の僕はNuxt.jsでプロジェクト最初からリリースまで携わりました。
今回はその経験と学んだことについて紹介したいと思います。

TL;DR

この記事は僕の経験からいくつかメモや学んだことを共通したいものです。
一番伝えたいのはこの注意点です。

項目

事前に知っておくべきこと

Nuxt.jsはVue.jsに基づいたフレームワークなので、もちろん事前にVue.js公式ライブラリ(vue, vuexやvue-router)の基礎知識が必要です。その以外に、Vueと同様、Nuxt.jsもライブサイクルがありますので、
以下のNuxtのサーバーサイドレンダリング(SSR)ライブサイクルを知っておいたほうがいいと思います。

nuxt-lifecycle.png

参考: https://medium.com/@onlykiosk/the-complete-nuxt-guide-940751e1a6a5

  • nuxtServerInit このアクションはVuexのストアに定義されたら、リクエストが来たら、ルートを問わずにNuxt.jsはそれを呼び出します。これはユーザー認証やサーバーサイドから共通のデータの取得などのために、使われます。

僕の場合はnuxtServerInitにリクエストのUserAgentでデバイスの判定、ユーザー認証や全ページの共通のデータ取得の処理を入れました。

注意: Nuxtの公式のドキュメントにも書いてありますが、 このnuxtServerInitアクションはPromiseを返すかasync/awaitを使う必要です。

Note: Asynchronous nuxtServerInit actions must return a Promise or leverage async/await to allow the nuxt server to wait on them.

  • middleware: 上記の図に書いてあるNuxt.jsのデフォルトのmiddlewareの以外に、カスタマイズのmiddlewareを作り、使うことができます。
  • validate: ページのパラメーターやクエリーのバリデーション
  • asyncDatafetchはページレンダリングする(コンポーネントを初期化する)前に、非同期の処理でデータを取得するためのメソッドです。

簡単設定でカスタマイズ、拡張しやすい

Nuxt.jsのメインの設定ファイルnuxt.config.jsで簡単に色々な設定を変更、追加できます。例えば: ページのhead(metaやtitleなど)、環境変数やwebpackなど

Router

Nuxt.jspagesディレクトリにコンポーネントを定義したら、自動的にルーティングを生成してくれるってNuxt.jsの一つ特徴ですが、
実際のアプリケーションを作る時に、リダイレクトのルートや一つページ(コンポーネント)に対する複数ルートなど場合があるでしょうか?
その時に、Routerはカスタマイズが必要になりますね。

Nuxt.jsvue-routerを使っているので、vue-routerのフォーマットと以下のような簡単設定でカスタマイズできます。

また、全体ページに適用したいmiddlewarenuxt.config.jsにも定義できます。

// router/customRoutes.js
const CUSTOM_ROUTES = [
  // 既存のページの別ルート
  {
   name: 'custom-event-page',
   path: '/custom/',
   component: '@/pages/event.vue'
  },
  // リダイレクトのルート
  {
   path: '/source_page/:id?',
   redirect: {
    path: '/destination_page/:id?',
    query: { hoge: 'fuga'}
   }
  }
]
// nuxt.config.js
const customRoutes = require('./router/customRoutes')

module.exports = {
 router: {
    middleware: 'commonMiddleware', // middleware/commonMiddleware.js
    extendRoutes (routes) {
      customRoutes.forEach(route => {
        routes.push(route)
      })
    }
  },
}

Plugins

Nuxt.jsプラグインはVue.jsアプリケーションがインスタンス化される前に実装されます。
外部のパッケージやVueプラグインをNuxt.jsアプリケーションで使用したい場合、プラグインを使う必要です。

SSRをサポートしないVueプラグインがありますので、その時にクライアント側のみ使うようにする必要です。

// nuxt.config.js
  plugins: [
    { src: '~/plugins/no-ssr-plugin', ssr: false },
  ],

そして、Vueインスタンスに注入したい場合、プラグインを使う必要です。
僕の場合は、ページのasyncDataでエラーの時に、エラーのハンドリングと非同期の処理でデータを取得するため使いました。

// plugins/error-handler.js
export default ({ store }, inject) => {
 inject('error', async (errorCode) => {
    await errorHandler(store, errorCode)
    // エラーコードによりエラーのページにリダイレクトする
    error({ statusCode: errorCode })
  })
}
// pages/component.vue
asyncData ({ app, store, params }) {
   // エラーの場合
   await app.$error(errorCode)
}

Modules

Nuxt.jsのコア機能を簡単に拡張し、インテグレーションを加えるNuxt.jsの拡張機能です。
この拡張機能のおかげで、Nuxtコミュニティが広くなって、たくさん便利な機能がシェアされています。

次はいくつか便利なモジュールを紹介したいと思います。

便利なモジュール

Nuxtコミュニティでたくさん便利なモジュールを作ってくれて、シェアされています。
その中に使っていた2つNuxt.jsチームの公式モジュールを紹介したいと思います。

@nuxt/axios

簡単にAxiosNuxt.jsとを統合するモジュールです。

こちらはNuxt.jsで簡単にAxiosが使えるだけではなく、プラグインでaxiosinterceptorsを登録できます。

// plugins/axios

export default function ({ $axios, res, store }) {
  // リクエストの前処理
  $axios.onRequest(config => {
    config.withCredentials = true
    return config
  })

  // エラーをハンドリングする
  $axios.onError(error => {
    const code = parseInt(error.response && error.response.status)
    if (code === 500) {
      // handle error
    }
    if (process.env.NODE_ENV !== 'production') console.error(error)
  })

  // 返ってくる時の処理
  $axios.onResponse(response => {
    // SSRの時に、responseのヘッダーをチェックして、クライアント側に返すヘッダーをセットする
    if (response.headers && response.headers['xxxx'] && process.server) {
      res.setHeader('yyyyy', response.headers['yyyy'])
    }
  })
}

@nuxtjs/google-tag-manager

Nuxt.jsアプリケーションでGoogle Tag Manager(GTM)やGoogle Analytics(GA)が使えるようにするモジュールです。

Vue.jsのSSRのWebアプリケーションですが、初回のアクセスだけサーバーサイドレンダリングされて、その以降通常のVue.jsのアプリケーションになるので、GTMやGAタグのスクリプトをここままに使えないです。

このモジュールを使って、GTMやGA側で以下の設定を適切に変更すれば、問題なく使えます。

GTMやGA側の設定は詳しくないけど、主な変更点はイベントトリガーだと思います。
このモジュールのデフォルトのページビューイベントはnuxtRouteです。

注意点

この記事で一番伝えたいことはNuxt.jsの使用の時の注意点です。以下の2つあります。

Vuexストアのstateの値はfunctionでなければならない

Vue.jsアプリケーションでVuexストアのstate定義はObjectで普通じゃないでしょうか?Vuexの公式exampleもそうですし。

ただし、Nuxt.jsのSSRでそうするとで不要な共有状態が発生する可能性があります。この問題が発生すれば、サーバーサイド側から取ってくるデータを対象外(ユーザー)に送ってしまう可能性があります。

詳しくはこのNuxt.jsgithubのissueを見てください。

Regardless of the mode, your state value should always be a function to avoid unwanted shared state on the server side.

↑この注意点はNuxt.js公式ページにも記載してあります。

参考: https://nuxtjs.org/guide/vuex-store/

  • ストア定義の例

BAD

// store/state.js
export default {
  state_hoge: null,
  state_fuga: null
}
// store/index.js
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import mutations from './mutations'
import * as actions from './actions'

const createStore = () => {
  return new Vuex.Store({
    state,
    getters,
    mutations,
    actions
  })
}

export default createStore

GOOD

// store/state.js
export default () => ({
  state_hoge: null,
  state_fuga: null
})
// store/index.js
import Vuex from 'vuex'
import createState from './state'
import getters from './getters'
import mutations from './mutations'
import * as actions from './actions'

const createStore = () => {
  return new Vuex.Store({
    state: createState(),
    getters,
    mutations,
    actions
  })
}

export default createStore

asyncDataのデータが再利用されてしまう

詳しくはこのNuxt.jsgithubのissueを見て下さい。

簡単説明なら、以下のようです。

eventページのasyncData関数がこんな感じです。

asyncData ({ $axios }) {
   const res = await $axios.$get('/hoge_api_path')
   const data = { common: res.data.common }
   if (res.data.special_flg) {
     data.isSpecial = true
     data.specialData = res.data.special_data
   }
}
  1. まず、 events/123にアクセスしたら、asyncDataの返り値は以下の通りです。
{
  common: "hogehoge",
  isSpecial: true,
  specialData: "hogedata"
}
  1. 次、events/456にアクセスしたら、asyncDataの返値は以下の通りです。
{
  common: "fugafuga"
}

Nuxt.jsasyncDataのデータとVueコンポーネントに定義しているデータにマージして、Vueコンポーネントのデータになる仕組みで、再利用されてしまうので、events/456ページのデータは以下のようになってしまいます。

{
  common: "fugafuga",
  isSpecial: true,
  specialData: "hogedata"
}

ですから、asyncDataの返り値はifで分けず、絶対に同じフォーマットみたいでなければならない。

GOOD

asyncData ({ $axios }) {
   const res = await $axios.$get('/hoge_api_path')
   return {
     common: res.data.common,
     isSpecial: res.data.is_special,
     specialData: res.data.is_special ? res.data.special_data : null
   }
}

終わり

今回は自分のメモみたいでNuxt.jsでVue.jsのSSRのWebアプリケーションの制作について学んだことや得たことを紹介しました。
ご拝読いただきありがとうございます。

次回はNuxt.js/Vue.jsのWebアプリケーションのパフォーマンスの最適化について書く予定です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした