Nuxt.jsアプリケーションの最適化

前回の[記事](https://qiita.com/trantan/items/d02eff9801ca97555ada)はNuxt.jsでVue.jsのSSRのWebアプリケーションの制作の経験について紹介しました。そのWebアプリケーションをリリースしたあと、まずしなければならなかったことはサイトのパフォーマンスの向上でした。
今回はその時に学んだことや経験について紹介したいと思います。

![node_modules.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/b16eb271-7c65-85ef-35b1-1ef86b186171.png)

## TL;DR
- [画像の遅延読み込み](#%E7%94%BB%E5%83%8F%E3%81%AE%E9%81%85%E5%BB%B6%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF)
- [外部のjavascriptライブラリーの最適化（`moment.js`と`font-awesome`）](#%E5%A4%96%E9%83%A8%E3%81%AEjs%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%83%BC%E3%81%AE%E6%9C%80%E9%81%A9%E5%8C%96)

## パフォーマンスが重要なのはなぜか？
まず、パフォーマンスの高いサイトはユーザーの体験がよくて、 パフォーマンスの低いサイトよりユーザーを引きつけられるでしょうか。

[Googleの調査結果](https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/)によると、ページロード時間が**10秒**削減したら、サイトに戻ってくるユーザー率が**123%**増加します。

![mobile-page-speed-new-industry-benchmarks-01-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/1f051ccb-20ed-74f4-5fe9-09abe26f0a0c.png)

実際の事例: Pinterest では、ユーザーが体感している待ち時間を**40%**削減した結果、[検索エンジンのトラフィックとサインアップ数が15%増加しました。]((https://medium.com/pinterest-engineering/driving-user-growth-with-performance-improvements-cfc50dafadd7?))

また、SEOが必要なウェブサイトなら、もっと重要になりますね。
Googleはウェブサイトを評価するために、パフォーマンス（`Page speed`）は[重要な評価点の一つ](https://webmasters.googleblog.com/2010/04/using-site-speed-in-web-search-ranking.html)のだからです。

## チェック
まず、ウェブサイトの問題や改善方法をチェックします。

チェックするツールがたくさんありますが、
以下はGoogleの公式のサイトやツールであり、結構有名でよく使われています。
- [PageSpeed Insight](https://developers.google.com/speed/pagespeed/insights)
- [Lighthouse](https://developers.google.com/web/tools/lighthouse)（今Chromeのデベロッパーツールの`audits`パネルでもチェックできるようになりました。）

上記のツールでチェックしたら、色々なレポートと、問題点と、そのページを改善する最適化案を出してくれます。

## パフォーマンス向上
サイトをチェックした後、レポートや改善案を参考して、サイトのパフォーマンスの改善を実施します。

### 画像の遅延読み込み
実はアプリケーションの開発の時に、既に画像の遅延読み込みを実装して、使いました。
ですが、`Lighthouse`ツールを使ったらサイトにある画像の中にどれが遅延読み込む必要なのか以下のようなオフスクリーンイメージ監査という形で指摘してくれます。

![offscreen-audit-1x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/d0df4918-2119-0af0-882d-a45f6ca7612f.png)

Vue.jsで画像の遅延読み込みを導入するには外部のVue.jsのプラグインがたくさんあり、簡単で導入できます。例えば: [Vue-Lazyload](https://github.com/hilongjw/vue-lazyload)

ただし、パフォーマンスの優先でもっと軽量な方法を使用したかったので、[Lozad.js](https://github.com/ApoorvSaxena/lozad.js/)という軽量なライブラリー（~1kb）を使って画像の遅延読み込み用のコンポーネントを作りました。

詳しく画像/動画の遅延読み込みについて知りたい方は[こちら](https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video)のGoogleの公式なガイドに参考してください。

### 外部のjsライブラリーの最適化
この改善の目的はアプリケーションのjsファイルのサイズを小さくすることです。

まず、使っているbundles（モジュール）のコンテンツやサイズを確認し、視覚化するために、`Nuxt.js`は既に[webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer)という便利なライブラリーを入れ、使っています。

`nuxt.config.js`ファイルで設定を以下のように変更して、`yarn nuxt build --analyze`コマンドを実行すると確認できます。

```javascript:nuxt.config.js
export default {
  build: {
    analyze: true
  }
}
```

結果は以下のような形です。
<img width="1272" alt="スクリーンショット 2019-12-20 12.22.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/a83e25d0-2908-7bcc-ad3e-06f7e8fc5fd3.png">

これをちゃんとチェックしたら、とりあえずどれが重いライブラリーなのかと、どのようにそのライブラリーの最適化できそうなのかわかりますね。

僕の場合、2つライブラリーの最適化をやっていました。

1.`moment.js`
**改善前**
<img width="615" alt="スクリーンショット 2019-12-20 12.21.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/47cfaffc-7a2e-5146-6d62-b5858a27d2d3.png">

↑`moment`はi18nのため全部のlocalesをロードしていますが、日本語(`ja`)しか使っていないので、日本語だけロードすれば良さそうですね。

<img width="973" alt="スクリーンショット 2019-12-20 12.32.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/3b8c7de3-5ca8-4f0f-effb-314c1dc7cf52.png">

↑`moment-timezone`用データファイルは重いですね。i18nのように日本の時間帯しか使っていないですので、日本の時間帯だけにすれば良さそうですね。

**改善方法**
上記の改修案を実現するためには、`ContextReplacementPlugin`と`NormalModuleReplacementPlugin`というwebpackの2つプラグインを使いました。

詳しくは以下のようです。

```javascript:nuxt.config.js
const webpack = require('webpack')
const path = require('path')
export default {
  build: {
    plugins: [
      new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /ja/),
      new webpack.NormalModuleReplacementPlugin(
        /moment-timezone[/\\]data[/\\]packed[/\\]latest.json$/,
        path.join(__dirname, '/config/data/momentTz.json')
      )
    ]
  }
}
```

```json:momentTz.json
{
  "version": "2018g",
	"zones": [
		"Asia/Tokyo|LMT JST JDT|-9i.X -90 -a0|0121212121|-3jE90 2qSo0 Rc0 1lc0 14o0 1zc0 Oo0 1zc0 Oo0|38e6"
  ],
  "links": [
		"Asia/Tokyo|Japan"
  ]
}
```

`momentTz.json`は`moment-timezone`のjsonデータファイルから日本時間帯用の設定だけを抜き出して、カスタマイズのファイルです。

**改善後**

<img width="389" alt="スクリーンショット 2019-12-20 12.51.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/442e6b26-4208-b7c9-e62a-a3ab859d6b73.png">
<img width="461" alt="スクリーンショット 2019-12-20 12.52.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/a1156889-4194-8266-e77c-7cba4425d826.png">

大分減らしましたね :rocket:

2.`font-awesome`
**改善前**
アプリケーションに`font-awesome`を導入するためには、[nuxt-fontawesome](https://github.com/vaso2/nuxt-fontawesome)というパーケージとデフォルト設定で使いました。

```javascript:nuxt.config.js
export default {
  modules: [
    ['nuxt-fontawesome', {
      imports: [
        {
          set: '@fortawesome/free-solid-svg-icons',
          icons: ['fas']
        },
        {
          set: '@fortawesome/free-regular-svg-icons',
          icons: ['far']
        }
      ]
    }]
  ]
}
```

デフォルトは全部`solid`と`regular`のfontセットをロードしてしまいます。
<img width="417" alt="スクリーンショット 2019-12-20 13.50.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/e3ec55a7-02ae-51f5-e490-fd49eb7ae01f.png">


**改善方法**
使用しているフォントを定義することで、そのフォントだけロードするようにします。

```javascript:nuxt.config.js
export default {
  modules: [
    ['nuxt-fontawesome', {
      imports: [
        {
          set: '@fortawesome/free-solid-svg-icons',
          icons: ['faAngleRight', 'faAngleLeft'] // 使用しているフォントの定義
        },
        {
          set: '@fortawesome/free-regular-svg-icons',
          icons: ['faAdjust', 'faArchive'] // 使用しているフォントの定義
        }
      ]
    }]
  ]
}
```


[nuxt-fontawesome](https://github.com/vaso2/nuxt-fontawesome)のコードを見てみたら、それは[tree-shaking](https://developers.google.com/web/fundamentals/performance/optimizing-javascript/tree-shaking)技術のアイデアです。

例:

- BAD

```javascript
// 全部の関数インポート
import arrayUtils from "array-utils";
```

- GOOD

```javascript
// 必要な関数だけインポート
import { unique, implode, explode } from "array-utils";
```

**改善後**
<img width="460" alt="スクリーンショット 2019-12-20 13.59.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117115/077526ae-950b-96e8-6ebb-849ea317fcb0.png">

上記の2つの改善で最適化した後、**vendors.app.js**ファイルが**(Stat size: 2.15 MiB)**から**(Stat size: 249 KiB)**に削減しました。:tada::tada::tada:

## 終わりに
上記に紹介した改善方法の以外に、`Dynamic import`でVueコンポーネントの遅延読み込みの対策などもやってみましたが、あまり効果が出てなかったので、今回それを紹介しません。

ウェブサイトのパフォーマンス向上は困難なことで、色々改善をやったけど、ちょっと効果が出ることがよくありますね。:cold_sweat:

ですから、他の良い改善方法がある方はぜび共有お願いいたします。

## 参考
- https://www.thinkwithgoogle.com/marketing-resources/data-measurement/mobile-page-speed-new-industry-benchmarks/
- https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video
- https://developers.google.com/web/fundamentals/performance/optimizing-javascript/tree-shaking
- https://nuxtjs.org