わいのNuxt.jsアプリケーションは遅かった。

この記事は [今年イチ！お勧めしたいテクニック by ゆめみ feat.やめ太郎 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yumemi_no1)の3日目の記事です。
プログラミング初級者がNuxt.jsの高速化するのにストレージにキャッシュしてみたら、色々捗った事を書きます。
わいさん風を取り入れてみました。少しなまりが間違っている箇所があるかもしれませんが、ご容赦下さい。

# Nuxt.jsでSSRしたら最速なのか？
## ある日の転職したくて、ポートフォリオアプリを作っているワイ。
ワイ　「Nuxt.jsでサーバーサイドレンダリングが簡単に出来るみたいやな。」
ワイ　「サーバーサイドレンダリングで、アイソーモフィックでユニバーサルなシングルページアプリケーションで作ってみました、とか面接で言えば転職間違いなしやな」
ワイ　「ヘッドレスCMSが流行ってるみたいやな。Blogアプリとかいいやん。作って自分で勉強した事をアウトプット出来るしな。（本当はどうせ、作って満足して、アウトプット出来ないBlogアプリを量産するんやけどな。。）」
ワイ　「GraphCMSってのがあるやん、GraphQLでリクエスト
すればJsonでレスポンス返してくれるヘッドレスCMSやん。これ使えば、GraphQLは使えますって見栄張れるな。」
ワイ　「FirebaseのFunctionsでSSRしたらなんかかっこいいやん！私はサーバレスですって言えるやん。」
ワイ　「Bootstrap-vue使って、GraphqlのリクエストはAppoloClient使って、FontAwesome使って、GoogleFont使って・・・・・・よし出来た！」
ワイ　「なんでもNuxt.js用にライブラリ作ってくれてるもんで、便利やな！」

ワイ　「これでわいも爆速で最強のSSRやでって言えるな。よし、LightHouseしてオール１００点ですっていうのをスクショ取ってQiitaで見せびらかしたろ」

![first_lighthouse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/37ad0fee-a0ae-cfcd-29b8-4e8a479cb36a.png)

ワイ　「全部緑色ちゃうやん！」
ワイ　「見せびらかせやんやん！」

## LightHouseのパフォーマンス

上記のスクショでは、６５点でしたが、凝ったWebアプリを作れば作るほどもっと悪いスコアが出る方も多いのではないでしょうか？（私はそうでした。）
パフォーマンス以外の部分は、LightHouseの評価するポイントを埋めていけば100点に近づいていきます。足し算の努力で対応出来ます。
しかし、パフォーマンスだけは、足し算だけではスコアが上がりません。この記事では、パフォーマンスを100点に近づけていく事をテーマに個人開発でポートフォリオを作っている方が、取り入れる時に参考になりそうな事を考えてみました。

## まずLightHouseについて

Lighthouse（ライトハウス）は、もともとGoogleが提供しているChrome拡張機能でしたが、現在はChromeに標準搭載されて、デベロッパーツールのAuditsから使えるので、拡張機能はインストールしなくて大丈夫です。Goolgeが考えるWebサイト、Webアプリ
（モバイルも含む）が目指す先の道しるべとしてのLighthouse(灯台）という意味です。

### スコアは変動する
LightHouseのスコアは、変動をできるだけ最小限に押さえられる様に作られているそうです。
しかし、インターネット回線の速度、実行するデバイスの違い、アクセスするサイト全てに手を加えるChrome拡張を入れていたり、異なるバージョンのLightHouseで計測する等様々な理由で変動します。出来るだけ同じ環境で、複数回計測し、平均点をあげて行けば良いのかなと思います。

### LightHouseのバージョンによる違い。

スコアの算出方法としては、Googleが考えるベストプラクティスなので、googleが考えるユーザー知覚パフォーマンスに影響を与える度合いというもので、これから説明する、いくつかの指標に重み付けをして算出されています。
さらにこの重み付けは、LightHouseのバージョンが変われば変わってしまいます。
つまり一度、高スコアを出しても、Googleさんの考えが変わればスコアは変動するという事です。
なのでLightHouseのスコアを高くする事を目指すのがとにかく良いということでは無いという事は理解しておく必要があります。
参考URL：https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics

## Perfomanceのスコアを算出する為にどんな指標があるのか？
詳しくは、https://developers.google.com/web/tools/lighthouse に書かれています。
この記事では、ざっくりとした説明にしますので、気になった部分は、こちらを確認してみて下さい。
<img width="657" alt="スクリーンショット 2019-12-02 13.14.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/612e275c-e69b-b1d1-20f9-d05a42b65f5b.png">


## Chrome Dev Summit 2018の画像
<img width="636" alt="スクリーンショット 2019-12-02 16.46.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/040e8248-64f5-6eed-c163-1828fe32382b.png">
Paul Irish at Chrome Dev Summit 2018

画像をお借りして私のざっくりとした理解ですが、

- FirstContentfulPaintがリクエストから、クライアントからアクションを起こせる状態になるまでの時間で、
- その状態から一番時間がかかるタイミングで一番時間がかかるアクションを取った時のアプリからのレスポンスにかかる時間がMaxPotentialFirstInputDelayである。
- Time to Interactviはページが完全にインタラクティブになるまでの時間です。

NuxtでSSRをすれば、Htmlをサーバ側で、生成してくるので、FirstContentfulPaintは早いが、早い分、MaxPotentialFirstInputDelayは大きく出るという事になると考えられます。MaxPotentialFirstInputDelayの計測値にGoogleが重きをおけばおくほどスコアは下がっていくというように考えられます。
## ざっくりスコアを上げる為に主なできる事

- クライアントのリクエストスタートからサーバのレスポンスを早くする。アーキテクチャの見直し。

- なるべくCSS・JS・画像データを軽くして読み込みを早くする。

- ソースの読み込み時にブロッキングが発生している所がないか確認、あればブロッキングしないように出来ないかやってみる。
lazyloadとか。prefetchとかPrerenderとか。



## 今回のわい君が思いついたアプリ

<img width="1053" alt="スクリーンショット 2019-12-02 14.00.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/ef5fbd5a-f892-8f4e-5ca1-67bd07123167.png">

## FirebaseFunctionsでSSRする時は要注意

FirebaseFunctionsをFirebaseHostingをドメインを結びつけて使用する際には現在、
<font color="Crimson">リージョンはUSセントラルしか使えません！
</font>
つまり、クライアントが日本にあるとすると、USセントラルのFirebaseにリクエストを送ると、東京リージョンのGraphCMSにデータを取りに行き、USセントラルでから日本にレスポンスを返してきます。
<font color="Crimson">
世界１周旅行です。
</font>


GraphCMSのリージョンをUSにすればいいんじゃ無いかと思った方もいるかと思います。

しかし、CMSをUSに持っていっても、今度は、CMSで管理している画像を保存しているストレージがUSに行ってしまいます。

初期表示時にデータを入れてSSRをしたい時には、SSRを行うアプリケーションの核となる部分はクライアントに近くないと考えることが多くなると感じました。

世にあるCMSや、情報を発信してくれるAPIや、画像を圧縮してくれるAPIをアプリケーションに取り込む際は、リアルタイムにクライアントのレスポンスに応じて、APIサービスを使用するのでは無く、あらかじめストレージにキャッシュしておけるならしておくというような使い方をしなくちゃなと思いました。

## CMSのデータをストレージにキャッシュさせるといい事いっぱい

<img width="1052" alt="スクリーンショット 2019-12-02 14.51.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/00f2a188-2166-0f4e-06f3-90a30a2878af.png">

CMSで新しい記事を書いた時、または編集した時にフックしてFirebaseFunctionsをから、GraphCMSからデータを取り、ストレージに保存します。

すると、クライアントがBlogページへリクエストを送った時には、データはストレージに取りに行きます。先ほどのアメリカ日本を往復するデータ取得の処理は、クライアントがBlogを閲覧する時には走らなくなりました。

***また、CMSへリクエストを投げる為に使っていた、ApolloClientもFunctionで、使うのでNuxtには必要なくなりました。少しデータが軽くなりました。***

***さらに勝手に気を利かせてGCPがCDNにキャッシュしてくれているみたい！***

クライアントで欲しいデータを外部APIへ取りに行ったり、そのデータを加工したりするのは、FaaS,PaaS等にデプロイした自家製APIに任せて、使いたいデータの形式にしてストレージにキャッシュしておくのがいいなと思いました。

Blogアプリで考えられるのは、Markdown形式で書かれたコンテンツデータをHtmlにパースする事をストレージにキャッシュする前に処理出来ないか等が考えられると思います。まだ出来ていませんが、これは何を作る時でも色々考えられる事だと思いました。

## `pwa-module`は入れる

Nuxt.jsは簡単にPWAを実現出来ます。簡単すぎて、serviceworkerについては別途学習しないと何も理解出来ないくらいです。この導入で、PWAモジュールに含まれるworkboxモジュールによってサービスワーカーが導入され、キャッシュが効くようになります。 Googleは現在、PWA推しのようです。

```console
$ yarn add @nuxtjs/pwa
```

```js:nuxt.config.js
modules: [
    '@nuxtjs/pwa
]
```

## とにかく軽くする

```bash
$ nuxt build --analyze
```
しまくります。削減可能な部分を考えたり、重いライブラリを軽量ライブラリに置き換えたりします。

難しいことは考えずにとにかく軽くする方法を考えました。

### FontAwesomeは使うアイコンだけ読み込むようにする。
FontAwesomeの導入方法は様々ありますが、いっぺんに全てのアイコンを読み込んでしまうものがあります。
よく使われているものでも、`@fortawesome`,`nuxt-fontawesome`,`@nuxt/font-awesome`,`vue-fontawesome`,`vue-awesome`とか他にもあるかもしれません。
初級者向けの記事を参考にすると、出来るだけ簡単に単手順で全て取り込めて、どのページでも全てのフリーのアイコンを使えるようなものを採用することになってしまいます。

私は、軽量化するに当たって、`@fortawesome/vue-fontawesome`を使ってプラグインで使うものんだけ読み込んでみました。これでも大きな軽量化になりました。使用するコンポーネントだけで読み込んだほうが良いのか、`vue-fontawesome`以外も使ってサイズの違いを確認してみます。


nuxt.config.js で プラグイン で読み込む

```js:nuxt.config.js
css: [
       '@fortawesome/fontawesome-svg-core/styles.css'
     ],
plugins: [
    {
      src: '~plugins/font-awesome',
      ssr: false
    },
     ],
```

使用するものだけ読み込む

```js:plugins/fontawesome.js
import Vue from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { faCalendarAlt, faTags } from '@fortawesome/free-solid-svg-icons'

library.add(faCalendarAlt, faTags)

Vue.component('fa-icon', FontAwesomeIcon)

Vue.config.productionTip = false
```

## コンポーネントライブラリは重い
今回、CSSフレームワークとして、使ったのはBootstrap-vueでした。BootStrap-vueは、コンポーネントのライブラリとなっています。CSSだけでなくJavascriptも内部で使っています。これが使ってない部分も全て取り込まれてしまいます。自分でJSを書く必要はありますが、ここをCSSのみのライブラリに変更出来れば軽量化出来ます。
<img width="991" alt="スクリーンショット 2019-12-02 16.02.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/9d09b2ee-b46c-9daa-a3ef-2d5f344ccd82.png">

## まだやりたい事は全部できてませんが、

まだ、bootstrap-vueは置き換えていません。
<img width="1110" alt="スクリーンショット 2019-12-03 0.11.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/9027a5a7-52a6-e746-b0ef-cb0e30c8aac0.png">
さらにGraphCMSでは記事の内容を更新したり、新規投稿したりした時にHookして何かアクションを起こしたりする事は無料では出来ないことが発覚しました。とりあえずの間に合わせで、CMSを更新したら、ターミナルからrefreshコマンドを打てばリフレッシュ出来るようにしました。ポンコツ仕様ですが、自分でCMSを作りたいというモチベーションが出来たので無駄では無いと言い聞かせます。

結果、９０点以上は平均して出るようになりました。

<img width="1638" alt="スクリーンショット 2019-10-04 17.07.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252625/b0988517-ded8-bd09-c848-c97e9793bf5c.png">

ワイ「これで緑色にはなったな」
ワイ「データ駆動やからこそ、もらうデータのパターンが決まっていてキャッシュしやすいものはキャッシュ最強やな」
ワイ「それにしてもLightHouseを使って`$ nuxt build --analyze`叩いているうちに重いライブラリをみると敵に見えてきてしまうようになってしもたで。」

## まとめ

ブログサイトやポートフォリオページなんかの更新がそれほどリアルタイム性が必要無いものは、ストレージにキャッシュする事でいい事満載だなと思いました。フロントエンドの役割が大きくなっていくにつれて、Firebaseにバックエンドは任せて、初級者はなんでもライブラリをフロントエンドに詰め込みまくってしまいがちです。
とりあえず、FirebaseStoreのデータを全部読み込んで、複雑なデータ処理もフロントエンドで行なったりしがちです。

私がその典型でした。

しかし、出来るだけサーバーサイドにデータ処理のロジックを回したり、外部APIをから手に入れるデータがある際は、キャッシュしたりする事でフロントエンドが軽量に出来るという視点を持たなくてはならないなと感じました。

今回の内容には直接関係無いかもしれませんが、最終的な成果物です。
ポートフォリオ的Blogアプリですが、作って満足して、内容は更新できていません。汗

最終的なBlogサイトのデモページ：https://nuxt-deploy-test-teshima.appspot.com/
コード：https://github.com/yujiteshima/nuxt_blog_LT