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

前のモジュールでは、クリティカル レンダリング パスの背後にある理論と、レンダリング ブロック リソースとパーサー ブロック リソースがページの初期レンダリングを遅らせる仕組みについて説明しました。これで、クリティカル レンダリング パスの背後にある理論を理解できたので、クリティカル レンダリング パスを最適化する手法を学ぶ準備が整いました。

ページが読み込まれると、HTML 内で多くのリソースが参照されます。これらのリソースは、CSS を通じてページの見た目とレイアウトを提供し、JavaScript を通じてインタラクティブ性を提供します。このモジュールでは、これらのリソースと、それらがページの読み込み時間にどのように影響するかに関連する重要なコンセプトをいくつか取り上げます。

レンダリング ブロック

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

上記の動画では、スタイリングが適用されていないページが表示される FOUC が短時間発生しています。その後、ページの CSS がネットワークからの読み込みを完了すると、すべてのスタイルが適用され、スタイルのないバージョンのページがスタイルのあるバージョンにすぐに置き換えられます。

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

パーサーのブロック

パーサーをブロックするリソースは、HTML パーサーを中断します。たとえば、async 属性や defer 属性のない <script> 要素などです。パーサーが <script> 要素を検出すると、ブラウザはスクリプトを評価して実行してから、残りの HTML の解析に進む必要があります。これは仕様です。スクリプトは、DOM がまだ構築されている間に 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 を最適化すると、ページ全体の読み込み時間に大きな影響を与える可能性があります。

Minification

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 をダウンロードしている可能性があります。

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

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

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

CSS の @import 宣言を避ける

@import 宣言は便利に見えますが、CSS では避けるべきです。

/* 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">

ほとんどの場合、<link rel="stylesheet"> 要素を使用して @import を置き換えることができます。<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 スクリプトはマークアップに表示されている順序で実行されます。

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

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

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

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

Minification

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> 要素。
正解です。

ブラウザのプリロード スキャナの役割は何ですか?

これは、DOM パーサーよりも早くリソースを検出するために、生のマークアップを調べてリソースを検出するセカンダリ HTML パーサーです。
正解です。
HTML リソース内の <link rel="preload"> 要素を検出します。
もう一度お試しください。

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

Flash of Unstyled Content(FOUC)を防ぐため。
もう一度お試しください。
JavaScript の評価は CPU 使用率が非常に高いタスクであり、HTML の解析を一時停止すると、CPU がスクリプトの読み込みを完了するための帯域幅が増えるためです。
もう一度お試しください。
スクリプトは DOM を変更したり、アクセスしたりできるためです。
正解です。

次へ: リソースヒントによるブラウザの支援

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