Vuetifyを使ったSPAで最低限のPageSpeedInsightsスコアを獲得するまでに取り組んだこと

5 min read

この記事は何

  • ReactでもVueでも、何かしらのUIフレームワークを活用することが多いかと思います。私が個人開発したサイトComicLakeでは、Vue(Nuxt)+Vuetifyを活用していますが、何の工夫も打たないとページ表示速度の観点でPageSpeedInsightsさんからボロクソに言われます笑🤣
  • ページ表示速度改善のためのTipsはいろいろなところで見かけますが、情報が古かったり散らばっていたりするので、現時点の情報をまとめておくことにも価値があると思い、私の開発体験を通じて得たTipsをまとめておきます。

本記事で取り扱う内容

  • 外部リソース(アイコン、フォント)の読み込み最適化
  • 不要なVuetifyリソースの除外
  • 描画コストがデカすぎるコンポーネントの扱い

前提条件

各モジュールの導入方法などは個別に調べて下さい

  • nuxt 2.14.12
  • vuetify 2.4.6
  • nuxt-webfontloader 1.1.0
  • @mdi/js 5.9.55
  • nuxt-purgecss 1.0.0

ピュアなVue.jsでも大筋は変わらないと思いますが、どこに設定を書くかとかが変わってきます。

本編

徒然なるままに🖋

外部リソース(アイコン、フォント)の読み込み最適化

必ず怒られるのが外部リソースの読み込みです。処置するファイルごとに分けて、設定方法を記述していきます。

nuxt.config.js

nuxt.config.js
export default{
  buildModules: [
    '@nuxtjs/vuetify',
  ],
  
  modules: [
    `nuxt-webfontloader`,
  ],
  
  vuetify: {
    defaultAssets: false,
    optionsPath: './vuetify.options.js',
    customVariables: ['~/assets/variables.scss'],
  },
  // just example below
  webfontloader: {
    custom: {
      families: ['Roboto:n3,n4,n5,n7', 'Saira Extra Condensed:n7'],
      urls: [
        'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap',
        'https://fonts.googleapis.com/css2?family=Saira+Extra+Condensed:wght@700&display=swap&text=ComicLake',
      ],
    },
  },
}
  • デフォルトではアイコン、フォントともにCDNを使って外部から読み込みます。その方法ではリソースの読み込み速度が不利なのでdefaultAssets:falseとして無効化します。
  • その場合アイコンのオプションを指定する必要があるため、optionsPath:ファイル名で指定します。
  • フォントについては「前提」に記載したnuxt-webfontloaderを使って非同期読み込みを行います。その設定もnuxt.config.jsの中に記載します。
    • ポイントは①使用するフォントの中でも使用する書体のみに絞ってインポートを行うこと、②使う文字が限られている場合(例えばロゴ等)はtext=***の形式で読み込む文字を限定することの2点です。
    • デフォルトのフォントファミリーを変えたい場合などは、customVariablesに記載した別ファイル内で設定を行います。

よく見かけるフォントの最適化手段として、Webフォントをダウンロードして自鯖に置くというものもあります。その労力に対し、上記手法でも十分点数が獲得できると判断し、webfontloaderを使った方法を採用しています。

日本語フォントでも対策方法は同じなのですが、個人的には、日本語Webフォントを無闇矢鱈に使うと、読み込み速度の観点で厳しい戦いになると思っています。ロゴや見出しなど、使用する文字を限定できる場合を除いては、ローカルフォントを使いこなすのが良いのでは無いかと思います。ローカルフォントを含めたフォントファミリー指定方法については後述します。

vuetify.options.js

vuetify.options.js
export default {
  // for production (not using mdi cdn)
  icons: {
    iconfont: 'mdiSvg',
  },
}
  • Vuetify公式にも記載されているように、使用するアイコンだけを限定してimportするのが好ましいです。そのための前準備として上記設定を行います。

https://vuetifyjs.com/en/features/icon-fonts/#material-design-icons-js-svg
  • 各.vueファイル内での使い方は上記公式ページを参照下さい。
  • 使用可能なアイコン一覧は、個人的には下記リポジトリが一番調べやすかったです。

https://pictogrammers.github.io/@mdi/font/1.1.34/

./assets/variables.scss

日本語フォントなど、ローカルフォントを活用する際に、デフォルト設定を上書きするには下記のように記述する。

variables.scss
$body-font-family: 'Roboto', 'Hiragino Kaku Gothic ProN', 'ヒラギノ角ゴ ProN W3', メイリオ, 'MS Pゴシック', Meiryo, Arial, Tahoma, sans-serif;

不要なVuetifyリソースの除外

何も処置せずとも、productionモードでは使用しているコンポーネントだけがbundleされるはずなのですが、CSSについては全てがbundleされてしまいます。次の方法で対処します。[1]

nuxt.config.js

nuxt.config.js
export default{
  buildModules: [
    'nuxt-purgecss',
  ],

  purgeCSS: {
    enabled: process.env.NODE_ENV === 'production',
    paths: [
      'components/**/*.vue',
      'layouts/**/*.vue',
      'pages/**/*.vue',
      'plugins/**/*.js',
      './node_modules/vuetify/dist/vuetify.js',
      'assets/**/*.scss',
    ],
    styleExtensions: ['.css'],
    whitelist: ['v-application', 'v-application--wrap', 'layout', 'row', 'col'],
    whitelistPatterns: [
      /^v-((?!application).)*$/,
      /^theme--*/,
      /.*-transition/,
      /^justify-*/,
      /^p*-[0-9]/,
      /^m*-[0-9]/,
      /^text--*/,
      /--text$/,
      /^row-*/,
      /^col-*/,
    ],
    whitelistPatternsChildren: [/^v-((?!application).)*$/, /^theme--*/],
    extractors: [
      {
        extractor: (content) => content.match(/[A-z0-9-:\\/]+/g) || [],
        extensions: ['html', 'vue', 'js'],
      },
    ],
  },
}

描画コストがデカすぎるコンポーネントの扱い

  • 私が開発したWebサイトは下記キャプチャ画像のように、画像やボタン・タイトルを含んだリストを大量に並べます。
  • 大量にコンポーネントを描画することは、描画コストが高いです。対処方法としては①virtual-scrollを使って画面に表示される範囲のみ描画する②各コンポーネントの描画コストを下げるの二択があるかと思います。

virtual-scrollの使用

vuetifyにもv-virtual-scrollコンポーネントがあるため、そちらを使ってあげると良いかと思います。

各コンポーネントの描画コスト削減

下記内容はあくまでVue.js v2系の話です。v3以降はパフォーマンスの改善が行われているため、functional componentを使うメリットは少ないと公式ページでもアナウンスされています。

何らかの理由(例えば私の場合、2-columnがv-virtual-scrollではうまく実装できなかった)でvirtual-scrollを使わない場合はこちらの手段を選択する必要があります。

  • 試しにv-btnコンポーネントを含むリストを100個ほど並べてスクロール頂くと分かるのですが、画面描画が追いつきません(良好な状態を60fpsとすると、30fpsとかまで落ちるケースがありました)。
  • Vuetifyの全てのコンポーネントがそうなるわけではありません。内部的にfunctional componentを使っているかどうかが運命の分かれ道だと推察しています。
  • Vue開発者ツールで調べてみると分かりますが、functionalではないコンポーネントが大量に描画されると、描画速度がガタ落ちしてきます…。
  • この場合、極論ですがvuetifyを使わずに実装する必要があります。私の場合、下記のようなカードコンポーネントを自前でゴリゴリ実装しました…。ただ自前で実装すれば良いわけではなく、忘れずにfunctional componentで実装していきます。
    • 自前といっても、Vuetifyのcssヘルパーは使っても大丈夫ですので、うまいこと活用すると良いかと思います。
    • 日本語で読めるfunctional componentについての記事はこちらを参照。

おわりに

記事の途中にも書きましたが、Vue.js v2系だからこそ苦労している点もあるので、v3系に上げたらどうなるかも、追い追い検証してみたいと思います。

キャプチャ画像を見てサイトに興味を持っていただけましたら、下記にもアクセスをお願いします 🚀

https://www.comiclake.net
脚注
  1. こちらを参考にさせて頂いています。https://qiita.com/nogutk/items/58370cd8a713111be9bc ↩︎

Discussion

ログインするとコメントできます