JavaScript フレームワークでのリソースのインライン化

JavaScript エコシステム全体で Largest Contentful Paint を改善する。

プロジェクト Aurora の一環として、Google は一般的なウェブ フレームワークと連携して、Core Web Vitals に基づく優れたパフォーマンスを実現しています。Angular と Next.js には、すでにフォントのインライン化が導入されています。これについては、この記事の最初のパートで説明しています。次に取り上げる最適化は重要な CSS インライン化です。これは Angular CLI でデフォルトで有効になり、Nuxt.js での実装が進行中です。

フォントのインライン化

何百ものアプリケーションを分析した結果、Aurora のチームは、デベロッパーが index.html<head> 要素でフォントを参照することで、アプリケーションにフォントを含めることが多いことがわかりました。マテリアル アイコンを含めると、次のように表示されます。

<!doctype html>
<html lang="en">
<head>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  ...
</html>

このパターンは完全に有効で機能しますが、アプリのレンダリングがブロックされ、余分なリクエストが発生します。上の HTML で参照されているスタイルシートのソースコードを確認して、何が起きているのかを理解しましょう。

/* fallback */
@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/font.woff2) format('woff2');
}

.material-icons {
  /*...*/
}

font-face 定義が、fonts.gstatic.com でホストされている外部ファイルをどのように参照しているかに注意してください。アプリケーションを読み込む際、ブラウザは最初に、head で参照されている元のスタイルシートをダウンロードする必要があります。

ウェブサイトがサーバーにリクエストを送信して外部スタイルシートをダウンロードする方法を示す画像
最初に、ウェブサイトでフォント スタイルシートが読み込まれます。

次に、ブラウザは woff2 ファイルをダウンロードし、最終的にアプリのレンダリングを続行できます。

フォント スタイルシート用とフォント ファイル用の 2 つのリクエストを示す画像。
次に、フォントの読み込みをリクエストします。

最適化する場合は、ビルド時に最初のスタイルシートをダウンロードし、index.html にインライン化します。これにより、実行時に CDN へのラウンド トリップ全体がスキップされるため、ブロック時間を短縮できます。

アプリケーションを構築すると、CDN にリクエストが送信されます。これにより、スタイルシートが取得されて HTML ファイルにインライン化され、<link rel=preconnect> がドメインに追加されます。この手法を適用すると、次の結果が得られます。

<!doctype html>
<html lang="en">
<head>
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin >
  <style type="text/css">
  @font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/font.woff2) format('woff2');}.material-icons{/*...*/}</style>
  ...
</html>

フォントのインライン化が Next.js と Angular で利用可能に

フレームワークのデベロッパーが基盤となるツールに最適化を実装すると、既存のアプリケーションと新しいアプリケーションで簡単に最適化できるようになり、エコシステム全体が改善されます。

この改善は、Next.js v10.2 と Angular v11 からデフォルトで有効になっています。どちらも Google フォントと Adobe フォントのインライン化をサポートしています。Angular では、後者を v12.2 で導入する予定です。

GitHub の Next.js でのフォント インライン化の実装と、Angular のコンテキストにおけるこの最適化について説明する動画をご覧ください。

クリティカル CSS のインライン化

また、重要な CSS をインライン化して、First Contentful Paint(FCP)Largest Contentful Paint(LCP)の指標を改善することもできます。ページの重要な CSS には、最初のレンダリングで使用されるすべてのスタイルが含まれています。このトピックの詳細については、クリティカルでない CSS を先送りするをご覧ください。

多くのアプリが同期的にスタイルを読み込むことで、アプリのレンダリングが妨げられることがわかっています。簡単な修正策として、スタイルを非同期で読み込む方法があります。media="all" を使用してスクリプトを読み込むのではなく、media 属性の値を print に設定し、読み込みが完了したら、属性値を all に置き換えます。

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">

ただし、この方法では、スタイル設定されていないコンテンツがちらつくことがあります。

スタイルが読み込まれると、ページがちらつくように見えます。

上の動画は、スタイルを非同期で読み込むページのレンダリングを示しています。ちらつきは、ブラウザが最初にスタイルのダウンロードを開始し、その後に続く HTML をレンダリングするために発生します。ブラウザはスタイルをダウンロードすると、リンク要素の onload イベントをトリガーして、media 属性を all に更新し、スタイルを DOM に適用します。

HTML をレンダリングしてからスタイルを適用するまでの間、ページの一部のスタイル設定が解除されます。 ブラウザでスタイルを使用すると、ちらつきが発生します。これはユーザー エクスペリエンスに悪影響を及ぼし、Cumulative Layout Shift(CLS)の回帰につながります。

重要な CSS インライン化と非同期スタイル読み込みにより、読み込み動作を改善できます。critters ツールは、スタイルシート内のセレクタを確認して HTML と照合することで、ページで使用されているスタイルを検出します。一致するスタイルが見つかると、対応するスタイルを重要な CSS の一部と見なし、インライン化します。

例を見てみましょう。

すべきでないこと
<head>
   <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>
/* styles.css */
section button.primary {
  /* ... */
}
.list {
  /* ... */
}

インライン化前の例

上記の例では、クリッターは styles.css のコンテンツを読み取って解析します。その後、2 つのセレクタが HTML と照合され、section button.primary が使用されていることが検出されます。最後に、動物が対応するスタイルをページの <head> にインライン化すると、次のようになります。

推奨事項
<head>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
  <style>
  section button.primary {
    /* ... */
  }
  </style>
</head>
<body>
  <section>
    <button class="primary"></button>
  </section>
</body>

インライン化後の例。

重要な CSS を HTML にインライン化すると、ページのちらつきが解消されます。

CSS のインライン化後のページ読み込み。

クリティカル CSS インライン化が Angular で利用できるようになりました。また、v12 ではデフォルトで有効になっています。v11 を使用している場合は、angular.jsoninlineCritical プロパティを true に設定して有効にします。Next.js でこの機能を有効にするには、next.config.jsexperimental: { optimizeCss: true } を追加します。

まとめ

この投稿では、Chrome とウェブ フレームワークのコラボレーションについていくつか触れました。フレームワーク作成者の方で、Google が技術で取り組んだ問題の一部を認識している方は、この調査結果から、同様のパフォーマンス最適化を適用するヒントをいただければ幸いです。

改善の詳細Google が Core Web Vitals に対して実施している最適化作業の包括的なリストについては、Aurora のご紹介の投稿をご覧ください。