リソースの読み込みを最適化する

前のモジュールでは、クリティカル レンダリング パスの背景にある理論と、レンダリング ブロック リソースとパーサー ブロック リソースによってページの最初のレンダリングが遅延する仕組みについて説明しました。ここまでで、その背後にある理論をいくつか理解しました。次は、クリティカル レンダリング パスを最適化するための手法について学びましょう。

ページが読み込まれると、HTML 内で多くのリソースが参照されます。これらのリソースは、CSS を通じてページの外観やレイアウトを提供し、JavaScript を通じてインタラクティブな要素を提供します。このモジュールでは、これらのリソースに関する重要なコンセプトと、ページの読み込み時間に及ぼす影響について説明します。

レンダリング ブロック

前のモジュールで説明したように、CSS はレンダリング ブロック リソースです。CSS オブジェクト モデル(CSSOM)が構築されるまで、ブラウザによるコンテンツのレンダリングをブロックするからです。スタイル指定のないコンテンツのフラッシュ(FOUC)(ユーザー エクスペリエンスの観点から望ましくない)を防ぐために、ブラウザはレンダリングをブロックします。

前の動画では、スタイル設定なしでページを確認できる短い FOUC があります。その後、ページの CSS がネットワークからの読み込みを完了すると、すべてのスタイルが適用され、スタイルが適用されていないページがすぐにスタイル付きバージョンに置き換えられます。

一般的に FOUC は普段は見られないものですが、CSS がダウンロードされてページに適用されるまで、ブラウザがページのレンダリングをブロックする理由を理解するために理解しておくことが重要です。レンダリング ブロックは必ずしも望ましくないわけではありませんが、CSS を最適化してその期間を最小限に抑える必要があります。

パーサーのブロック

パーサー ブロック リソースは HTML パーサーを中断するリソースです(async または defer 属性がない <script> 要素など)。パーサーで <script> 要素が検出されると、ブラウザは HTML の残りの部分の解析に進む前に、スクリプトを評価して実行する必要があります。これは設計によるものです。スクリプトは、DOM の作成中に DOM を変更したりアクセスしたりする場合があるためです。

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

外部 JavaScript ファイル(async または defer なし)を使用する場合、ファイルが検出されてからダウンロード、解析、実行が行われるまで、パーサーはブロックされます。インライン JavaScript を使用する場合も、インライン スクリプトが解析されて実行されるまで、パーサーはブロックされます。

プリロード スキャナ

プリロード スキャナは、セカンダリ HTML パーサーという形でブラウザを最適化するためのツールです。未加工の HTML レスポンスをスキャンして、プライマリ HTML パーサーで検出される前にリソースを検出し、推測により取得します。たとえば、プリロード スキャナを使用すると、CSS や JavaScript などのリソースの取得および処理中に HTML パーサーがブロックされても、ブラウザは <img> 要素で指定されたリソースのダウンロードを開始できます。

プリロード スキャナを活用するには、サーバーによって送信される HTML マークアップに重要なリソースを含める必要があります。次のリソース読み込みパターンは、プリロード スキャナで検出できません。

  • background-image プロパティを使用して CSS によって読み込まれた画像。これらの画像参照は CSS 内にあるため、プリロード スキャナでは検出できません。
  • JavaScript を使用して DOM に挿入される <script> 要素のマークアップの形式で、動的に読み込まれるスクリプト、または動的 import() を使用して読み込まれたモジュール。
  • JavaScript を使用してクライアントでレンダリングされた HTML。このようなマークアップは、JavaScript リソースの文字列に含まれており、プリロード スキャナでは検出できません。
  • CSS の @import 宣言。

これらのリソース読み込みパターンはすべて、遅れて検出されるリソースであるため、プリロード スキャナの恩恵を受けません。可能な限り避けてください。ただし、このようなパターンを回避することが不可能な場合は、preload ヒントを使用してリソース検出の遅延を回避できる場合があります。

CSS

CSS では、ページの表示形式とレイアウトを決定します。前述のように、CSS はレンダリング ブロック リソースであるため、CSS の最適化はページの読み込み時間全体に大きく影響する可能性があります。

圧縮

CSS ファイルを最小化すると、CSS リソースのファイルサイズが小さくなり、ダウンロード時間が短縮されます。これを行うには、主に、スペースや非表示の文字などのソース CSS ファイルからコンテンツを削除し、その結果を新しく最適化されたファイルに出力します。

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

最も基本的な形式では CSS 圧縮が効果的な最適化であり、ウェブサイトの FCP、場合によっては LCP を改善できる可能性があります。バンドラなどのツールを使用して、本番環境のビルドで自動的にこの最適化を実行できます。

使用していない CSS を削除する

コンテンツをレンダリングする前に、ブラウザではすべてのスタイル シートをダウンロードして解析する必要があります。解析が完了するまでの時間には、現在のページで使用されていないスタイルも含まれます。すべての CSS リソースを 1 つのファイルにまとめるバンドラを使用している場合は、ユーザーが現在のページのレンダリングに必要以上の CSS をダウンロードする可能性が高くなります。

現在のページで使用されていない CSS を検出するには、Chrome DevTools のカバレッジツールを使用します。

Chrome DevTools のカバレッジ ツールのスクリーンショット。下部のペインで CSS ファイルが選択されており、現在のページ レイアウトで使用されていない CSS が相当量あることが表示されています。
Chrome DevTools のカバレッジ ツールは、現在のページで使用されていない CSS(および JavaScript)を検出するのに役立ちます。大規模な CSS バンドルを配布してページのレンダリングを遅らせるのではなく、CSS ファイルを複数のリソースに分割して異なるページで読み込むために使用できます。

未使用の CSS を削除すると、ダウンロード時間が短縮されるだけでなく、ブラウザで処理する CSS ルールの数が減るため、レンダリング ツリーの構築も最適化されます。

CSS の @import 宣言を回避する

便利に思えるかもしれませんが、CSS では @import 宣言を使用しないでください。

/* Don't do this: */
@import url('style.css');

HTML 内での <link> 要素の動作と同様に、CSS で @import 宣言を使用すると、スタイルシート内から外部 CSS リソースをインポートできます。2 つのアプローチの主な違いは、HTML <link> 要素は HTML レスポンスの一部であるため、@import 宣言でダウンロードされた CSS ファイルよりもずっと早く発見されることです。

これは、@import 宣言が検出されるためには、宣言を含む CSS ファイルをにダウンロードする必要があるためです。その結果、いわゆるリクエスト チェーン(CSS の場合)が発生して、ページが最初にレンダリングされるまでの時間が遅延します。もう 1 つの欠点は、@import 宣言を使用して読み込まれたスタイル シートがプリロード スキャナで検出できず、遅れて検出されるレンダリング ブロック リソースになることです。

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

ほとんどの場合、@import<link rel="stylesheet"> 要素で置き換えることができます。<link> 要素を使用すると、スタイルシートを同時にダウンロードでき、全体の読み込み時間が短縮されます。一方、@import 宣言の場合は、スタイルシートが連続してダウンロードされます。

クリティカルな CSS をインライン化する

CSS ファイルのダウンロードに時間がかかると、ページの FCP が増加する可能性があります。ドキュメント <head> で重要なスタイルをインライン化すると、CSS リソースのネットワーク リクエストがなくなります。正しく行うと、ユーザーのブラウザ キャッシュの準備が整っていない場合の初期読み込み時間を改善できます。残りの CSS は、非同期に読み込むか、<body> 要素の末尾に追加できます。

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

ただし、大量の CSS をインライン化すると、最初の HTML レスポンスのバイト数が増加します。多くの場合、HTML リソースは長時間、あるいはまったくキャッシュできないので、外部スタイルシートで同じ CSS を使用する可能性のある後続のページではインライン CSS がキャッシュされません。ページのパフォーマンスをテストして測定し、このトレードオフを考慮する価値があることを確認してください。

CSS のデモ

JavaScript

JavaScript は、ウェブでのインタラクティビティのほとんどを左右しますが、それには代償が伴います。 JavaScript の配布が多すぎると、ページの読み込み時のウェブページの応答が遅くなり、応答性の問題につながることさえあります。どちらもユーザーにとって不快な動作です。

レンダリング ブロック JavaScript

defer 属性または async 属性なしで <script> 要素を読み込むと、ブラウザはスクリプトがダウンロード、解析、実行されるまで解析とレンダリングをブロックします。同様に、インライン スクリプトは、スクリプトが解析されて実行されるまでパーサーをブロックします。

asyncdefer

asyncdefer を使用すると、HTML パーサーをブロックすることなく外部スクリプトを読み込めますが、type="module" を指定したスクリプト(インライン スクリプトを含む)は自動的に遅延します。ただし、asyncdefer には、理解しておくべき重要な違いがいくつかあります。

さまざまなスクリプト読み込みメカニズムの図。async、defer、type=&#39;module&#39;、および 3 つすべての組み合わせなど、使用されるさまざまな属性に基づくパーサー、フェッチ、実行のロールが詳しく説明されています。
出典: https://html.spec.whatwg.org/multipage/scripting.html

async で読み込まれたスクリプトは、ダウンロードされるとすぐに解析されて実行されます。一方、defer で読み込まれたスクリプトは、HTML ドキュメントの解析が終了すると実行されます。これは、ブラウザの DOMContentLoaded イベントと同時に行われます。また、async スクリプトは順不同で実行されることがあり、defer スクリプトはマークアップに記述された順序で実行されます。

クライアントサイド レンダリング

一般に、重要なコンテンツやページの LCP 要素のレンダリングには JavaScript を使用しないようにします。これはクライアントサイド レンダリングと呼ばれ、シングルページ アプリケーション(SPA)で幅広く使用されている技術です。

JavaScript によってレンダリングされたマークアップは、プリロード スキャナを迂回します。クライアントがレンダリングするマークアップ内に含まれるリソースは、プリロード スキャナでは検出できないためです。これにより、LCP 画像などの重要なリソースのダウンロードが遅れる可能性があります。ブラウザは、スクリプトが実行されて要素を DOM に追加した後にのみ、LCP 画像のダウンロードを開始します。つまり、スクリプトは検出、ダウンロード、解析が完了した後にのみ実行できます。これは「クリティカル リクエスト チェーン」と呼ばれ、回避する必要があります。

また、JavaScript を使用してマークアップをレンダリングすると、ナビゲーション リクエストに応じてサーバーからダウンロードされるマークアップよりも、時間のかかるタスクが生成される可能性が高くなります。クライアントサイドでの HTML レンダリングを大量に使用すると、操作のレイテンシに悪影響を及ぼす可能性があります。これは特に、ページの DOM が非常に大きい場合に当てはまります。これにより、JavaScript が DOM を変更する際に、レンダリング処理の負荷が大きくなります。

圧縮

CSS と同様に、JavaScript を最小化するとスクリプト リソースのファイルサイズが小さくなります。これによりダウンロードが速くなり、ブラウザが JavaScript の解析とコンパイルのプロセスに速く移ることができます。

また、JavaScript の圧縮は、CSS などの他のアセットを圧縮するよりも一歩先に進みます。JavaScript を圧縮すると、スペース、タブ、コメントなどが取り除かれるだけでなく、ソース JavaScript 内の記号も短縮されます。このプロセスは「統合」とも呼ばれます。違いを確認するには、次の JavaScript ソースコードを使用します。

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

上記の JavaScript ソースコードを改変すると、結果は次のコード スニペットのようになります。

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

上記のスニペットでは、ソース内の人間が読める形式の変数 scriptElementt に短縮されています。大量のスクリプトに対して適用した場合、ウェブサイトの本番環境用 JavaScript が提供する機能に影響を与えることなく、コストを大幅に削減できます。

バンドラを使用してウェブサイトのソースコードを処理している場合、本番環境のビルドでは多くの場合、初期化が自動的に行われます。また、Terser などのユーグルリファイアは高度な構成が可能で、ウグライフィケーション アルゴリズムの積極性を微調整して最大限に節約できます。ただし、通常は、出力サイズと機能の保持の適切なバランスを取るには、任意の統合ツールのデフォルトで十分です。

JavaScript のデモ

理解度テスト

ブラウザで複数の CSS ファイルを読み込む最適な方法はどれですか。

CSS の @import 宣言。
もう一度お試しください。
複数の <link> 要素。
正解です。

ブラウザのプリロード スキャナはどのような機能ですか。

これはセカンダリ HTML パーサーの 1 つであり、未加工のマークアップを調べて、DOM パーサーに先駆けてリソースを検出し、検出を早期に行います。
正解です。
HTML リソース内の <link rel="preload"> 要素を検出します。
もう一度お試しください。

JavaScript リソースをダウンロードするときに、ブラウザでデフォルトで HTML の解析が一時的にブロックされるのはなぜですか?

スタイル設定されていないコンテンツのフラッシュ(FOUC)を防止するため。
もう一度お試しください。
JavaScript の評価は CPU 負荷が非常に高いタスクであり、HTML 解析を一時停止すると、スクリプトの読み込みを完了するための CPU の帯域幅が増えるためです。
もう一度お試しください。
スクリプトは DOM の変更やアクセスが可能なためです。
正解です。

次のトピック: リソースヒントでブラウザを支援する

<head> 要素に読み込まれたリソースが、最初のページ読み込みとさまざまな指標にどのように影響するかを理解したところで、次に進みましょう。次のモジュールでは、リソースヒントについて説明します。リソースヒントを使用しないブラウザの場合よりも早く、リソースの読み込みとクロスオリジン サーバーへの接続を早期に開始するための貴重なヒントをブラウザに提供する方法を学びます。