Skip to content
アルパカ プレス | IT・AI・ゲーム・動画制作ブログ ロゴ
Go back

M PLUS 2を維持したままPageSpeed Insightsを68点から98点まで改善した話

このブログのPageSpeed Insightsを確認したところ、デスクトップのパフォーマンススコアが68点まで下がっていました。

改善前

以前にもPageSpeed Insightsの改善は行っていたのですが、今回は日本語Webフォントの重さがかなり影響していました。

結論としては、本文フォントのM PLUS 2をやめずに、サブセット化することでスコアは68点から98点まで改善しました。

今回は、そのときに行った対応をメモしておきます。

先に結論

今回やったことは、M PLUS 2のサブセット化です。

もともとはAstroのfonts機能を使って、Google FontsからM PLUS 2を読み込んでいました。

ただ、日本語フォントは収録文字数が多いため、そのまま読み込むとフォントファイルがかなり大きくなります。

ビルド後のdist/_astro/fontsは約2.8MBありました。

そこで、サイト内で使っている文字だけを抽出し、M PLUS 2の軽量版フォントを作るようにしました。

結果として、配信するフォントは次のサイズになりました。

  • mplus2-400-subset.woff2: 約108KB
  • mplus2-500-subset.woff2: 約104KB
  • mplus2-700-subset.woff2: 約108KB

合計で約320KBです。

フォントだけを見ると、約2.8MBから約320KBまで減りました。

フォントは変えたくなかった

一番簡単なのは、Webフォントをやめてシステムフォントにすることです。

それならフォントのダウンロード自体がなくなるので、PageSpeed Insightsのスコアは上がりやすくなります。

ただ、このブログではM PLUS 2の見た目を気に入っていたので、フォント自体は変えたくありませんでした。

そこで、フォントの種類は変えずに、配信する文字だけを減らす方針にしました。

サブセット化とは

サブセット化は、フォントに含まれる文字を必要なものだけに絞る方法です。

日本語フォントには、ひらがな、カタカナ、漢字、記号など大量の文字が入っています。

しかし、実際に自分のブログで使っている文字はその一部です。

そこで、記事本文、タイトル、説明文、UI文言などから使用文字を集め、その文字だけを含むwoff2を作ります。

今回のブログでは、src/public/配下のテキストファイルを読み取り、使用文字を抽出するようにしました。

静的サイトだからやりやすい

この方法は、Astroでほぼ完全に静的なページを生成しているブログだからやりやすい方法です。

このブログでは、記事本文、タイトル、タグ、説明文、UI文言がビルド時点でほぼ決まっています。

そのため、ビルド前にリポジトリ内の文字をスキャンすれば、サイト上で使う文字をだいたい集められます。

一方で、ユーザー投稿、コメント、管理画面からの動的コンテンツ、APIレスポンスで本文が変わるサイトでは注意が必要です。

ビルド後に未知の文字が表示される場合、その文字はサブセットフォントに含まれません。

その場合は、常用漢字やよく使う記号を広めに含める、動的部分だけシステムフォントにする、サブセット化しないフォントを別で用意する、といった対策が必要になります。

今回は静的ブログなので、全記事と固定文言を対象にしてサブセット化する方針で十分だと判断しました。

元フォントを用意する

まず、Google Fontsの公式リポジトリからM PLUS 2の元フォントを取得しました。

配置先は次のようにしました。

vendor/fonts/MPLUS2-wght.ttf
vendor/fonts/OFL.txt

MPLUS2-wght.ttfは、weightを持つ可変フォントです。

ライセンスファイルのOFL.txtも一緒に置いています。

サブセット生成スクリプトを作る

サブセット生成用に、次のスクリプトを追加しました。

scripts/subset-fonts.py

このスクリプトでは、次の処理を行っています。

  1. src/public/配下のテキストファイルを読む
  2. 使用されている文字を集める
  3. M PLUS 2の可変フォントから400500700の静的フォントを作る
  4. pyftsubsetで使用文字だけを残したwoff2を出力する

実行コマンドは次のようにしました。

npm run fonts:subset

中ではuvを使ってfonttoolsbrotliを一時的に読み込みます。

"fonts:subset": "uv run --with fonttools --with brotli python scripts/subset-fonts.py"

Python環境はこのブログの開発ルールに合わせてuvを使っています。

Astro fontsをやめて自前配信にする

サブセットフォントを使うため、Astroのfonts設定からM PLUS 2を外しました。

以前はastro.config.tsで次のように設定していました。

experimental: {
  preserveScriptOrder: true,
  fonts: [
    {
      name: "M PLUS 2",
      cssVariable: "--font-mplus2",
      provider: fontProviders.google(),
      fallbacks: ["sans-serif"],
      weights: [400, 500, 700],
      styles: ["normal"],
    },
  ],
},

これをやめて、global.css@font-faceを定義します。

@font-face {
  font-family: "M PLUS 2 Subset";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/fonts/mplus2-400-subset.woff2") format("woff2");
}

500700も同じように定義しています。

Tailwind側で使うフォントも、次のように変更しました。

@theme inline {
  --font-app: "M PLUS 2 Subset", sans-serif;
}

これで、見た目はM PLUS 2のまま、配信するフォントだけ軽くできます。

preloadもサブセットフォントへ向ける

初期表示で使う400のフォントは、Layout.astroでpreloadするようにしました。

<link
  rel="preload"
  href="/fonts/mplus2-400-subset.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

PageSpeed Insightsでは、フォントがLCPやFCPに影響していました。

そのため、本文の基本weightである400だけは先に読み込ませています。

記事追加時の運用

サブセット化には注意点があります。

サブセットフォントには、抽出した文字しか入っていません。

新しい記事で未収録の文字を使うと、その文字だけ別フォントにフォールバックされます。

そのため、記事を追加したら次の流れで更新します。

npm run fonts:subset
npm run build

手作業で文字を指定するのではなく、スクリプトで全記事から再抽出するようにしたので、運用としてはそこまで重くありません。

改善結果

今回の対応後、PageSpeed Insightsのデスクトップスコアは68点から98点まで改善しました。

改善前

フォントサイズは次のように変わりました。

  • 変更前: dist/_astro/fonts 約2.8MB
  • 変更後: public/fonts 約320KB

フォントを変えずにここまで改善できたので、個人的にはかなり満足しています。

もちろん、Webフォントを完全にやめればさらに軽くできる可能性はあります。

ただ、今回はブログの見た目を維持することも大事だったので、サブセット化はちょうどよい落としどころでした。

まとめ

日本語Webフォントは見た目を整えやすい反面、そのまま使うとかなり重くなります。

特にM PLUS 2のように日本語グリフを多く含むフォントでは、PageSpeed Insightsのスコアにも大きく影響します。

今回やってよかったことは、次の3つです。

  1. Webフォントをやめる前に、サブセット化を試した
  2. 記事追加時に再生成できるようにスクリプト化した
  3. 静的ブログなので、リポジトリ内の文字をビルド前に抽出できた
  4. Astro fontsではなく、自前の@font-faceで配信した

スコアだけを追うならシステムフォントが一番簡単です。

でも、フォントの雰囲気を残したい場合は、サブセット化がかなり現実的な選択肢だと思います。


関連記事