キャッシュ

Vue の SSR は非常に高速ですが、コンポーネントインスタンスや仮想 DOM ノードの作成コストのため純粋な文字列ベースのテンプレートのパフォーマンスにはかないません。 SSR のパフォーマンスが重大である場合、キャッシュ戦略を賢く活用することで、応答時間が大幅に改善され、サーバーの負荷が軽減されます。

ページレベルでのキャッシュ

ほとんどの場合、サーバで描画されたアプリケーションは外部データに依存するため、コンテンツは本質的には動的であり長期間キャッシュすることはできません。しかしながら、コンテンツがユーザー固有のものでない場合、(すなわち、同一URLが常にすべてのユーザに対して同じコンテンツを描画する場合)、 マイクロキャッシング という戦略を活用してアプリケーションのトラフィック処理能力を劇的に改善します。

これは通常 Nginx レイヤーで行われますが、 Node.js で実装することも可能です:

const microCache = LRU({
  max: 100,
  maxAge: 1000 // 重要: コンテンツの登録内容は1秒後に期限切れになります
})

const isCacheable = req => {
  // リクエストがユーザー固有のものかどうかチェックするロジックを実装します
  // ユーザー固有でないページのみがキャッシュ可能です
}

server.get('*', (req, res) => {
  const cacheable = isCacheable(req)
  if (cacheable) {
    const hit = microCache.get(req.url)
    if (hit) {
      return res.end(hit)
    }
  }

  renderer.renderToString((err, html) => {
    res.end(html)
    if (cacheable) {
      microCache.set(req.url, html)
    }
  })
})

コンテンツは1秒間だけキャッシュされるため、ユーザーに古いコンテンツが表示されることはありません。ただし、これはサーバーがキャッシュされたページごとに秒間最大1回の完全描画を実行するだけであることを意味します。

コンポーネントレベルでのキャッシュ

vue-server-renderer には、コンポーネントレベルのキャッシュ機能が組み込まれています。それを有効にするにはレンダラを作成する際にキャッシュ実装を有効にする必要があります。代表的な使用例は lru-cache を渡すことです:

const LRU = require('lru-cache')

const renderer = createRenderer({
  cache: LRU({
    max: 10000,
    maxAge: ...
  })
})

次に serverCacheKey 関数を実装してコンポーネントをキャッシュすることが出来ます:

export default {
  name: 'item', // required
  props: ['item'],
  serverCacheKey: props => props.item.id,
  render (h) {
    return h('div', this.item.id)
  }
}

キャッシュ可能なコンポーネントは 一意の name オプションも定義しなければいけないことに注意してください。一意の名前を持つと、キャッシュキーがコンポーネントごとに異なるため、同一キーを返す2つのコンポーネントについて心配する必要はありません。

serverCacheKey から返されるキーは描画結果を表すのに十分な情報を含んでいる必要があります。描画結果が単に props.item.id によって決定される場合、上記は良い実装でしょう。しかしながら、同じIDを持つアイテムが時間の経過とともに変わる場合や描画結果が他のプロパティに依存する場合、 他の変数を考慮して getCacheKey の実装を修正する必要があります。

定数を返すと、コンポーネントは常にキャッシュされ、単なる静的なコンポーネントには効果的です。

いつコンポーネントキャッシュを使うか

描画中にレンダラがコンポーネントのキャッシュにヒットした場合、キャッシュされた結果をサブツリー全体で直接再利用します。 つまり、次の場合にコンポーネントをキャッシュ しない でください。

  • グローバルな状態に依存する子コンポーネントがあります。
  • 描画 context に副作用をもたらす子コンポーネントがあります。

したがって、コンポーネントのキャッシングは、パフォーマンスのボトルネックに取り組むために慎重に適用する必要があります。 ほとんどの場合、単一インスタンスのコンポーネントをキャッシュする必要はなく、すべきではありません。キャッシングに適した最も一般的なコンポーネントのタイプは、大きな v-for リストで繰り返されるコンポーネントです。 これらのコンポーネントは通常、データベースコレクション内のオブジェクトを元にするため、一意のIDと最後に更新されたタイムスタンプを合わせて使用してキャッシュキーを生成するという単純なキャッシュ戦略を使用できます:

serverCacheKey: props => props.item.id + '::' + props.item.last_updated