1 月末にリニューアルした本サイトについて、今回はウェブパフォーマンスを取り上げます。

モバイルデバイスが一般的になり、ウェブサイトにおけるパフォーマンス最適化はもはや必要不可欠ですが、少なくとも以下の側面から考えることができます。

  • ユーザビリティ: ユーザが欲しい情報を時間をかけずに取得できるか
  • アクセシビリティ: さまざまな通信環境で情報にアクセスできるか
  • サステナビリティ: 無駄なコンテンツやコードによって転送量が増えることで、CO2 を多く排出したり、電力消費によるデバイスの劣化を早めることがないか

もちろん、Google が提唱する Core Web Vitals をはじめとする、パフォーマンス指標にも影響するため、副次的には SEO への効果も見込めます。

本サイトに関しては、フレームワークに Astro を使用し、ホスティングを Cloudflare Pages とする静的なサイトなので、特別な対応をしなくても(というより、余計なことをしなければ)、おのずとパフォーマンスは良くなりますが、取り組んだ施策や今後の課題を記述します。

本サイトの「ホーム」を PageSpeed Insights で計測した、デスクトップとモバイルの Lighthouse スコアは以下のとおりです。

Lighthouse スコア(デスクトップ): パフォーマンス 98、ユーザー補助 100、おすすめの方法 100、SEO 100
Lighthouse スコア(デスクトップ)
Lighthouse スコア(モバイル): パフォーマンス 91、ユーザー補助 100、おすすめの方法 100、SEO 100
Lighthouse スコア(モバイル)

Google Chrome のデベロッパーツールでも同様に Lighthouse スコアを計測できますが、ブラウザの拡張機能がパフォーマンスに影響することがあるので注意が必要です。その場合には、シークレットモードで計測するか、Lighthouse CI を使用するのがよいでしょう。

なお、これらのスコアは一時的なものなので、一喜一憂せずに参考程度にとどめ、継続的に改善に取り組み続けることが重要です。

パフォーマンス最適化で基本となるのは Core Web Vitals の以下の 3 つの指標です。

  • LCP(Largest Contentful Paint、最大視覚コンテンツの表示時間)
  • FID(First Input Delay、初回入力までの遅延時間)
  • CLS(Cumulative Layout Shift、累積レイアウトシフト数)

LCP は 2.5s 以下、FID は 100ms 以下、CLS は 0.1 以下が良好なスコアのボーダーラインです。本サイトにおけるパフォーマンス最適化も、これらの Core Web Vitals の指標をベースとして実施しています。

LCP は問題が検出されやすく、パフォーマンス改善のメインになりやすい指標です。

画像ファイルの軽量化や遅延読み込み、ソースコードの最適化、リソース読み込みの優先度の指定、DOM の削減など、やるべきことは山ほどありますが、実施できるかどうかはウェブサイトの要件とのトレードオフになることが多いです。

FID はフィールドデータの指標のため、ラボデータを扱う Lighthouse では計測できません。PageSpeed Insights や CrUX API など、フィールドデータを利用できるツールで計測する必要があります。

もしくは、Lighthouse で計測できる TBT(Total Blocking Time、合計ブロック時間)で代用することもできます。

  • フィールドデータは実際のユーザによる匿名データで、ラボデータは Lighthouse などのツールによるシミュレートしたデータです。
  • フィールドデータである CrUX データは、対象となるウェブサイトに十分なトラフィックが蓄積されないと利用できず、本サイトでもまだ利用できない状態です。
  • FID よりもより包括的な INP(Interaction to Next Paint) という応答性に関する指標も提案されています。

CLS は比較的に対応方法がわかりやすく、取り組みやすい指標です。

ただし、全面的にウェブフォントを使用していたり、JS で動的にコンテンツを表示するようなケースでは、改善の難易度が高くなります。

これらの Web Vitals を踏まえて、本サイトで取り組んでいるパフォーマンス最適化の施策のいくつかを列挙します。

  • 画像サイズの指定
  • 画像の遅延読み込み
  • Priority Hints
  • WebP
  • SVG
  • ウェブフォント

<img> 要素に width height を指定することで、CLS が発生するのを防止しています。

ちなみに、この CLS を防止するために指定するサイズの値は、レスポンシブレイアウトにおいては、実際の画像サイズではなくてもアスペクト比(縦横比)が同じであれば大丈夫です。

ブラウザ(User-Agent)のスタイルシートでは、この width height の値に応じて aspect-ratio が指定されるためです。

User-Agent スタイルシート
img {
  aspect-ratio: attr(width) / attr(height);
}

このため、CSS が適用されないといった稀なケースで表示されるサイズを制御できますが、ほとんどの場合では、運用面を考慮して原則は実際の画像サイズを指定するのがよいでしょう。

スクロールしないと表示されない <img> 要素に loading="lazy"(遅延読み込み)を指定することで、余計なリソースの読み込みを回避し、表示速度を向上させています。

本サイトでは <img> のみですが、<video><iframe> といった要素にも適用できます。

なお、Abobe the fold(ファーストビュー)に表示される要素に対して loading="lazy" を指定すると、LCP を悪化させる原因となるため避けるべきです。

fetchpriority を使用することで、画像や CSS、JS などの特定のリソースの読み込みの優先度を相対的に高めたり、低めたりすることができます。

後述するウェブフォントでも <link> に対して使用しておりますが、<img> 要素に対しても、リソース読み込みの優先度を明示したい場合に fetchpriority を指定しています。

画像に fetchpriority を指定する例
<!-- 優先度の高い画像 -->
<img src="important.webp" fetchpriority="high">

<!-- 優先度の低い画像 -->
<img src="unimportant.webp" fetchpriority="low">

なお、本サイトでは使用していませんが、JS で非同期にリソースを読み込むときにも priority プロパティで明示できます。

Fetch API で priority を指定する例
// 優先度の高いリソース
fetch('/important.json', { priority: 'high' }).then(/* ... */);

// 優先度の低いリソース
fetch('/unimportant.json', { priority: 'low' }).then(/* ... */);

Priority Hints は現時点では試験的な機能であり、Chromium ベースのブラウザのみ対応しています。

デベロッパーツールで確認

見出し「デベロッパーツールで確認」

Chrome のデベロッパーツールの「Network」タブで「Priority」の項目を表示させることで、各要素にどの優先度(Priority)が適用されているかを確認することができます。

デベロッパーツールにて Img でフィルタリング、Priority でソートした例

この「Priority」の値が Priority Hints によって、どのように変化するかは以下の記事が参考になります。

画像ファイルには、WebP フォーマットを積極的に使用しています。

ただし、やみくもに WebP にするのではなく、書き出されるファイルサイズを見比べながら場合によっては JPEG などの他のフォーマットを採用しています。

アイコン類やファイルサイズを抑えられるイラストについては SVG で書き出しています。

なお、Illustrator や Figma といったデザインツールから書き出した際には、以下の最適化を行っています。

  • パスやテキストのアウトライン化
  • パスファインダを使用してオブジェクトの構造を単純化
  • SVGO でコード圧縮、もしくは直接不要なコードを削除
  • 画像として扱う場合には role="img"title を追加(アクセシビリティ対応)

本サイトで使用しているウェブフォントは、欧文フォントの「IBM Plex Mono」のみです。

「ホーム」のキャッチコピーでは、和文フォントの「IBM Plex Sans JP」も使っていますが、これはウェブフォントではなく SVG で表示しています。

「IBM Plex Mono」は Google Fonts API 経由で読み込んでいますが、CLS を最小限に抑えるために、以下のように指定しています。

Google Fonts API の読み込み
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" fetchpriority="high" onload="this.onload=null;this.rel='stylesheet'" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400&display=swap">

1、2 行目は Google Fonts のサイトで生成されるコードに含まれる preconnect の指定ですが、3 行目では preloadfetchpriority を使用して優先的に読み込まれるようにカスタマイズを加えています。

fetchpriority に関しては、ヒーローイメージ(メインビジュアル)などの LCP であれば high、優先度の低い装飾的な画像であれば low といったように使い分けることで、リソース読み込みの優先順位を明示することができます。

レスポンシブイメージ

見出し「レスポンシブイメージ」

現状、本サイトではビューポート幅や画面解像度によって画像を出し分ける、レスポンシブイメージのテクニックは使用しておりません。これは「Astro 編」でも言及しましたが、Astro の画像最適化のインテグレーションが確立していないことが一因です。

なお、レスポンシブイメージには、パフォーマンス上の理由と、アートディレクション上の理由に分けられます。

  • パフォーマンス: ビューポートや画面解像度(Retina)によって、最適なサイズの画像を出し分ける
  • アートディレクション: ビューポートの縦横比によって、最適な比率の画像を出し分ける

後者は特に、モバイル用のレイアウトとデスクトップ用のレイアウトで異なる画像を表示したい場合に用いられます。

Cache についても、現状は個別の指定をしていません。
今後、Cache-Control ヘッダに期限(TTL)を明示して、最適化していければと考えています。

フィールドデータの計測

見出し「フィールドデータの計測」

前述したとおり、本サイトではまだフィールドデータ(CrUX データ)が利用できるトラフィックが獲得できていないため、ラボデータしか利用できません。

トラフィックが蓄積されてフィールドデータが利用できるようになったら、これらのデータを有効活用できる施策を考えていきたいです。

継続的なパフォーマンス改善

見出し「継続的なパフォーマンス改善」

継続的にパフォーマンス測定と改善をしていく施策としては、Lighthouse CI を GitHub Actions で実行して自動的に Lighthouse スコアを計測する方法も検討しています。