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

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

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

レンダリング ブロック

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

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

一般的に、FOUC は通常発生しませんが、CSS がダウンロードされてページに適用されるまでブラウザがページのレンダリングをブロックする理由を理解できるように、この概念を理解することが重要です。レンダリング ブロックは必ずしも望ましくないわけではありませんが、CSS を最適化して維持時間を最小限に抑える必要があります。

パーサーのブロック

パーサー ブロック リソースが HTML パーサーを中断します(async または defer 属性のない <script> 要素など)。パーサーが <script> 要素を検出すると、ブラウザは HTML の残りの部分の解析に進む前に、スクリプトを評価して実行する必要があります。これは設計上、スクリプトの作成中に 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 内の記号も短縮されます。このプロセスは、uglification とも呼ばれます。次の 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 パーサーです。未加工のマークアップを調べて、DOM パーサーよりも早くリソースを検出するために、リソースを検出します。
正解です。
HTML リソース内の <link rel="preload"> 要素を検出します。
もう一度お試しください。

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

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

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

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