Nuxt.js プロジェクトで便利だったモジュール・テクニックなど

[Nuxt.js (1.4) を使ったプロダクトをローンチした](https://www.codeal.work/contents/archives/6448)ので、便利だったモジュール・テクニックなどをまとめます。

## Bootstrapを便利に使う

bootstrap 4 を便利に使うために、[Bootstrap Vue](https://bootstrap-vue.js.org/) が存在する。モジュールとしてロードできる仕組みが存在するので、以下の方法で利用が可能です。

```
yarn add bootstrap
yarn add bootstrap-vue
```

cssの設定で、CSSをそのまま利用するかを選択できる。 (デフォルトは `true`)

```js:nuxt.config.js
module.exports = {
  modules: [
    ['bootstrap-vue/nuxt', { css: false }]
  ]
}
```

今回、bootstrap のテーマを変更したいため、 `css: false` にして、以下の用に assets を読み込むことにしました。

```assets/bootstrap.scss
@import '~bootstrap/scss/bootstrap';

// bootstrap のデザインカスタマイズ

html {
  font-size: 14px;
}
// ...
```

```js:nuxt.config.js
module.exports = {
  css: ['@/assets/bootstrap.scss']
}
```

この方法だと、ファイルサイズがやや大きくなってしまうので、個別に必要な[コンポーネントとディレクティブを呼ぶ方法](https://bootstrap-vue.js.org/docs)を利用したいのですが、残念ながら Nuxt.js 上では動作しない問題があります。([Issue参照のこと](https://github.com/bootstrap-vue/bootstrap-vue/issues/1491))

### よく使うコンポーネントと、諸注意

[Modal](https://bootstrap-vue.js.org/docs/components/modal/)

モーダルダイアログを出したり、消したりする機能を提供する。`b-modal` コンポーネントの利用が便利。

[Form Checkbox](https://bootstrap-vue.js.org/docs/components/form-checkbox)

単体のチェックボックス、もしくはグループを管理することができる。`b-form-checkbox` にはIDを正しく付与しないと、SSRモードでは動作しない(チェックを入れようとしてもチェックが入らない)ので注意。解決策は2つあります。

1. IDを正しく付与する: `b-form-checkbox` を利用するときは、id属性を指定する。 (おすすめ)
2. `b-form-checkbox` を利用するときは、`no-ssr` コンポーネントを利用する。

```
<no-ssr>
  <b-form-checkbox v-model="check">Check</b-form-checkbox>
</no-ssr>
```

この現象は、`b-form-radio` コンポーネント利用時にも発生します。

## トーストUIの追加

Bootstrap4 には、トーストUIが存在しないため、別のモジュールを利用しました。
[@nuxtjs/toast](https://github.com/nuxt-community/modules/tree/master/packages/toast) モジュールを利用することで、[vue-toasted](https://github.com/shakee93/vue-toasted) を簡単に利用することができます。

```
yarn add @nuxtjs/toast
```

```js:nuxt.config.js
module.exports = {
  modules: [
    ['@nuxtjs/toast']
  ],
  // toasted のデフォルト挙動設定
  toast: {
    // 右上にtoastを表示
    position: 'top-right',
    // 特に指定しなくても5秒で消えるように設定
    duration: 5000
  }
}
```

あとは、アプリケーション内では、必要なタイミングで以下を実行するだけで、toastが利用できるようになります。

```js
this.$toast.show('保存しました')
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/61997/9da6b689-d63e-a6bb-d8b3-ab75de777c25.png)


## i18n

Nuxt.js 上で i18n を行うには、[vue-i18n](https://github.com/kazupon/vue-i18n) を利用するのが便利。翻訳データに YAML を利用するには、少し手を加える必要あり。 [nuxt.js で vue-i18n を使えるようにする](https://qiita.com/ooharabucyou/items/0e0801b4334edc761937) にて知見をまとめています。

## ユニバーサルなCookieの利用

Nuxt.js では、サーバサイドでの動作・クライアントサイドの動作が存在するので、Cookieを利用する場合は要注意でした。仮に、asyncData() にて Cookie を利用する場合は、以下のようにヘンテコなコードを書く必要が出てきてしまいます。

```vue
<script>
  export default {
    asyncData ({req}) {
      let hoge = null
      if (process.server) {
        // サーバサイドレンダリングの場合
        hoge = req.cookies.hoge
      } else {
        // クライアントの場合
        // document.cookie を parse するなど
      }
      // ...
    }
  }
</script>
```

Cookie情報のparseには、[vue-cookie](https://github.com/alfhen/vue-cookie)の利用などが、便利ですが、サーバサイドとクライアントサイドで条件分岐してしまう面倒さは避けられません。
そこで、[cookie-universal-nuxt](https://www.npmjs.com/package/cookie-universal-nuxt) を利用することで、この煩わしさから開放されます。

```
yarn add cookie-universal-nuxt
```

```nuxt.config.js
module.exports = {
  modules: [
    ['cookie-universal-nuxt', {parseJSON: false}]
  ]
}
```

```vue
<script>
  export default {
    asyncData({app}) {
      const hoge = app.$cookies.get('hoge')
      // ...
    }
  }
</script>
```

## Google Tag Manager の利用

Google Analytics の設定や、Intercom、各種マーケティングツールを利用については、開発者だけでなくマーケターによる設定などにも対応させるため、Google Tag Manager (GTM) を利用しています。
Nuxt.js で GTM を利用する場合、 `@nuxtjs/google-tag-manager` が便利です。

```
yarn add @nuxtjs/google-tag-manager
```

nuxt.config.js では、モジュール設定で、GTMのIDを指定する。

```nuxt.config.js
module.exports = {
  modules: [
    ['@nuxtjs/google-tag-manager', {id: 'GTM-XXXXX'}]
  ]
}
```

このとき、GTMは `NODE_ENV=production` の環境でしか動かないことに留意が必要です。

GTM経由で、ページ遷移時に pageview を発生させるために、plugins を作ります。
これは、悩みどころですが、ページタイトル(`title`)が、routerの afterEach が発生した直後では、変更されていないことがありました。`vue-meta` による反映タイミングの問題かと思いますが、Google Analytics はページタイトル情報を `dt` パラメータとして送付するため、違うものを送られるのは解析上困る可能性があります。
ここでは、500msほど後に、`changeurl` イベントを発生させる挙動にして対処しました。なかなか苦しい対処な気もします。

```plugins/gtm.js
export default ({app}) => {
  const DELAY_CHANGE_URL = 500
  let created = false
  const gtm = app.gtm = {
    push (object = {}) {
      if (!window.dataLayer) return
      window.dataLayer.push(object)
    }
  }

  gtm.push({event: 'pageview'})

  app.router.afterEach(() => {
    Vue.nextTick(() => {
      setTimeout(() => {
        if (created) gtm.push({event: 'changeurl'})
        created = true
      }, DELAY_CHANGE_URL)
    })
  })
}
```

```nuxt.config.js
module.exports = {
  plugins: [
    '~/plugins/gtm.js'
  ]
}
```

GTM のほうでは、changeurl, pageview イベントを受け、Google Analytics の pageview イベントを発生させるように設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/61997/f8ce15bc-7f4c-a132-d285-0d4598ff5d5f.png)


## AWS Lambda 上で動作させる

[以前紹介した方法](https://qiita.com/ooharabucyou/items/d53b4f06b8d055ef2292)で、AWS Lambda上で動作させ、特に問題なく1ヶ月が経過しました。ほぼタダでした。(悲しいような。嬉しいような。)
Nuxt.js の仕組みとして最初の1回目だけLambdaが実行され、ページ遷移時は呼び出されません。実行回数はPVというよりは、訪問回数で推測することができます。