Nuxt.js Vuex で lru-cache をつかってAPIコールを減らしてサイト速度を上げる

# 概要
この記事ではキャッシュを用いてVuexの値を保存し、nuxt.jsプロジェクトの速度改善を行いたいと思います、といっても実測をするわけではないです。ポートフォリオや個人開発サービス、プロトタイプなど比較的小さなアプリケーションであれば、特に気にしなくてもよいですが、大きなプロジェクト、たくさんのアクセスがくる、というようなアプリケーションであれば積極的に導入してみてもよいと思われます。

## 前提：component cache は？
nuxt.jsでは component cache という機能が簡単に使えます。コンポーネントを再利用できる優れものです。こうすることで、静的サイトのような速さを実現できると公式サイトには書いてあります。

Nuxt.jsで使うときに必要なnpm module
https://www.npmjs.com/package/@nuxtjs/component-cache

Vueのコンポーネントキャッシュに関する記事
https://ssr.vuejs.org/ja/guide/caching.html

しかしながら、Vuexから値を参照する場合（globalな値を参照する場合）は使えません。あんまり意味ない？（笑）

## 本記事を使うタイミング
ブログ記事もそうですし、トップページのコンテンツなど管理画面でたまーに変えたり、アルゴリズムで定期的に更新したりする、頻繁に値が変わらないものってありますよね。その内容を返却するAPIもあるはずです、したみたいな。

```
/api/v1/master
/api/v1/posts/helloworld
```

こういうのってどのユーザーにも同じものが配信されるんですね、それなのにNuxtサーバーから同じAPIリクエストを毎回送ってるのすこしもったいなくないですか？
この値をNuxtサーバーにキャッシュしちゃおうよ！というときに使えます。

## 本題
### lru-cacheのインストール

```
npm i lru-cache
```

https://www.npmjs.com/package/lru-cache

### lru-cacheをサーバーサイドで使えるようにする。
今回はshortCache、mediumCache、longCacheという３つのキャッシュインスタンスを用意します。それぞれキャッシュの保存期間が異なります。
nuxt.hook("vue-renderer:ssr:prepareContext")というものを用いることで、SSR時のContextにこれらを入れることができます。

nuxtプロジェクトのルートディレクトリに/modulesというディレクトリをつくってそこに入れておけば、管理が楽になります。node_modulesに対して自作のmodulesをつくる感じです。

``` javascript:/modules/lru-cache.js
import LRU from "lru-cache";

export default function lruCache() {
  const shortCache = new LRU({ maxAge: 1000 * 60 });
  const mediumCache = new LRU({ maxAge: 1000 * 60 * 30 });
  const longCache = new LRU({ maxAge: 1000 * 60 * 60 });
  this.nuxt.hook("vue-renderer:ssr:prepareContext", (ctx) => {
    ctx.$shortLruCache = shortCache;
    ctx.$mediumLruCache = mediumCache;
    ctx.$longLruCache = longCache;
  });
}

```
nuxt.config.jsに作ったmoduleを読み込むようにします。watchプロパティでmodules配下を参照できるようにしておくことで、ホットリロードができるようになります。

```javascript:nuxt.config.js
export default {
////省略
　modules: [
    "modules/lruCache",
  ],
　watch: ["~/modules/*"],
////省略
};
```

### プラグイン化して様々なファイルから参照しやすくする。
モジュールを導入するだけではVuexやViewファイルから参照するときに冗長になってしまうので、プラグインで簡単に使えるようにします。
ssrだけで機能するので、process.serverで条件分岐しないといけないのが注意です。

```javascript:plugins/lru-cache.js
export default function ({ ssrContext }, inject) {
  if (process.server) {
    const lru = new LRU(
      ssrContext.$shortLruCache,
      ssrContext.$mediumLruCache,
      ssrContext.$longLruCache
    );
    inject("lru", lru);
  }
}

class LRU {
  constructor(short, medium, long) {
    this.short = short;
    this.medium = medium;
    this.long = long;
  }

  getShortCache(key) {
    return this.short.get(key);
  }

  setShortCache(key, value) {
    this.short.set(key, value);
    return true;
  }

  getMediumCache(key) {
    return this.medium.get(key);
  }

  setMediumCache(key, value) {
    this.medium.set(key, value);
    return true;
  }

  getLongCache(key) {
    return this.long.get(key);
  }

  setLongCache(key, value) {
    this.long.set(key, value);
    return true;
  }
}
```
pluginの読み込みをnuxt.config.jsに追加します。

```javascript:nuxt.config.js
export default {
////省略
  plugins: [
    "@/plugins/lru-cache",
  ],
　modules: [
    "modules/lruCache",
  ],
　watch: [
    "~/modules/*"
  ],
////省略
};
```

### Vuexへの実装
アクションでfetchするときに、キャッシュがある場合はそれをセットし、ない場合はAPIから取得してキャッシュとVuexにセットするというような形になります。
これも、サーバーサイドのみでしか機能しないのでprocess.serverで条件分岐します。nuxt-linkなどでcsr時に新しいデータを取得したい場合は別途その実装が必要です。

```javascript:/store/master.js
export const state = () => ({
 hoge: [] 
})

export const mutations = {
 setHoge: (state, params) => {
  state.hoge = params
 }
}

export const actions = {
 async fetch({ commit }) {
  if (process.server) {
   const cachedData = this.$lru.getMediumCache("master-hoge")
   if (cachedData) {
    commit("setHoge", cachedData)
   } else {
    const { data } = await this.$axios.get("/master")
    this.$lru.setMediumCache("master-hoge", data)
    commit("setHoge", data)
   }
  }
 }
}

```

## まとめ
以上でlru-cacheを用いて定常的なデータを毎回APIサーバーに取りに行くことなく、セットできるようになります。
速度改善のキモはいかに無駄な動きを減らすかなので、こんな感じのやり方もあるんだと思ってくれた方は実際に使ってみてください。