クリティカル レンダリング パスのパフォーマンスを分析する

公開日: 2014 年 3 月 31 日

クリティカル レンダリング パスのパフォーマンスのボトルネックを特定して解決するには、よくある問題を十分に理解する必要があります。ページの最適化に役立つ一般的なパフォーマンス パターンを特定するためのガイド付きツアーです。

クリティカル レンダリング パスを最適化すると、ブラウザでのページが可能な限り早く描画されます。ページの表示速度が上がると、エンゲージメント、閲覧ページ数、コンバージョン率の向上につながります。訪問者が空白の画面を表示する時間を最小限に抑えるには、どのリソースをどの順序で読み込むかを最適化する必要があります。

このプロセスを説明するために、可能な限りシンプルなケースから始め、徐々にリソース、スタイル、アプリケーション ロジックを追加してページを構築します。その過程で、ケースごとの最適化を行い、失敗しやすいポイントついても説明します。

ここまでは、リソース(CSS、JS、HTML ファイル)が処理できるようになるとブラウザで何が起こるかを説明してきました。ここでは、キャッシュまたはネットワークからリソースを取得するのにかかる時間については考慮していません。ここでは、次の前提条件があるとします。

  • サーバーへのネットワーク ラウンド トリップ(伝播レイテンシ)には 100 ミリ秒かかります。
  • サーバーの応答時間は、HTML ドキュメントで 100 ミリ秒、他のすべてのファイルで 10 ミリ秒です。

Hello World の体験

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試してみる

まずは CSS と JavaScript は使わずに、基本的な HTML マークアップと 1 つの画像から始めましょう。次に、Chrome DevTools でネットワーク ペインを開き、リソース ウォーターフォールを確認します。

CRP

予想どおり、HTML ファイルのダウンロードに約 200 ミリ秒かかっています。青色の線の透明な部分は、ブラウザがレスポンス バイトを受信せずにネットワーク上で待機する時間を表し、実線部分は最初のレスポンス バイトを受信してからダウンロードを終了する時間を示しています。HTML のダウンロードはサイズが小さい(4K 未満)ため、1 回のラウンドトリップでファイル全体を取得できます。そのため、HTML ドキュメントを取得するための所要時間は約 200 ms です。この時間の半分はネットワーク上で待機しており、残りの半分はサーバーの応答を待っています。

HTML コンテンツが利用可能になると、ブラウザはバイトを解析してトークンに変換し、DOM ツリーを構築します。DevTools の下の方には、便宜のために、DOMContentLoaded イベントの時間(216 ms)が表示されています。これは、青色の縦線に相当します。HTML ダウンロードの終了と青色の縦線(DOMContentLoaded)の差が、ブラウザで DOM ツリーを構築するのにかかった所要時間であり、今回の場合、数ミリ秒にすぎません。

「awesome photo」が domContentLoaded イベントをブロックしていない点にも注目してください。結局、ページ上の各アセットを待つことなく、レンダリング ツリーを構築し、ページを描画することもできます。First Paint を高速化するうえで、すべてのリソースが重要であるわけではありません。実際、クリティカル レンダリング パスについて説明するときには、通常は HTML マークアップ、CSS、JavaScript がそれに該当します。画像は、初回のページ レンダリングをブロックしませんが、できる限り早く画像がレンダリングされるように配慮する必要はあります。

ただし、load イベント(onload とも呼ばれます)は画像によってブロックされます。DevTools では、onload イベントは 335 ms 時点でレポートされています。onload イベントは、ページに必要なすべてのリソースのダウンロードと処理が完了した時点を示すことを思い出してください。この時点で、ブラウザの読み込み中スピナー(ウォーターフォールの赤い縦線)の回転が停止します。

JavaScript と CSS をサンプルに追加する

「Hello World サンプル」ページは、一見するとシンプルに見えますが、内部ではさまざまな処理が実行されています。実際には HTML 以外の要素も必要になります。CSS スタイルシートと 1 つ以上のスクリプトを組み込んで、ページに相互作用を追加することはよくあります。この両者をサンプルに追加して、どうなるか見てみましょう。

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>

試してみる

JavaScript と CSS を追加する前:

DOM CRP

JavaScript と CSS あり:

DOM、CSSOM、JS

外部 CSS ファイルと JavaScript ファイルを追加すると、2 つのリクエストがウォーターフォールに追加されます。ブラウザは、ほぼ同時にすべてをディスパッチしています。ただし、domContentLoaded イベントと onload イベントのタイミングの差は大幅に縮まっています。

なぜですか?

  • 前述の HTML のみのサンプルとは異なり、今回は CSS ファイルを取得して解析し、CSSOM を構築する必要があります。また、レンダリング ツリーの構築には、DOM と CSSOM の両方が必要です。
  • このページにはパーサー ブロック JavaScript ファイルも含まれているため、CSS ファイルがダウンロードされて解析されるまで domContentLoaded イベントはブロックされます。JavaScript が CSSOM にクエリを実行する可能性があるため、JavaScript を実行する前に、CSS ファイルがダウンロードされるまでブロックする必要があります。

外部スクリプトをインライン スクリプトに置き換えるとどうなりますか?スクリプトをインラインでページに直接組み込んだとしても、CSSOM が構築されるまで、ブラウザはスクリプトを実行できません。つまり、インライン JavaScript もパーサー ブロックになります。

CSS をブロックしても、インライン スクリプトの方がページのレンダリングが高速になるでしょうか。試してどうなるか見てみましょう。

外部 JavaScript:

DOM、CSSOM、JS

インライン JavaScript:

DOM、CSSOM、インライン JS

リクエストは 1 つ減りますが、onloaddomContentLoaded のタイミングは実質同じです。その理由は、ご存知のとおり、JavaScript がインラインであっても外部ファイルであっても、大きな違いはありません。どちらの場合も、ブラウザは script タグに遭遇するとブロックして、CSSOM が構築されるまで待機します。また、最初のサンプルでは、CSS と JavaScript がブラウザによって同時にダウンロードされ、ほぼ同時にダウンロードが完了していました。この例では、JavaScript コードをインライン化してもあまり役に立ちません。しかし、ページのレンダリングを高速化する方法はいくつかあります。

まず、すべてのインライン スクリプトはパーサー ブロックですが、外部スクリプトの場合は async 属性を追加してパーサーのブロックを解除できることを思い出してください。インライン化を元に戻して、試してみましょう。

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>

試してみる

パーサー ブロック(外部)JavaScript:

DOM、CSSOM、JS

非同期(外部)JavaScript:

DOM、CSSOM、非同期 JS

よくなりました。domContentLoaded イベントは、HTML が解析された直後に呼び出されます。ブラウザは JavaScript をブロックしないことを認識しており、他のパーサー ブロック スクリプトがないため、CSSOM の構築も並行して進められます。

別の方法として、CSS と JavaScript の両方をインライン化するというアプローチもあります。

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

試してみる

DOM、インライン CSS、インライン JS

domContentLoaded の時間は、前のサンプルとほぼ同じです。JavaScript を非同期にする代わりに、CSS と JavaScript の両方を直接ページにインライン化しています。これにより、HTML ページが大きくなりますが、ブラウザが外部リソースの取得を待機する必要がないというメリットがあります。すべての情報がページ内に収まるようにします

ご覧のように、ごく基本的なページであっても、クリティカル レンダリング パスの最適化は容易ではありません。異なるリソース間の依存関係グラフを理解し、どのリソースが「クリティカル」であるかを特定する必要があります。それらのリソースをページに組み込む方法は さまざまに異なるものから選択する必要がありますただし、ページごとに違いがあるため、対策は 1 つではありません。最適な戦略を特定するには、このようなプロセスを自身で実践する必要があります。

それでは、一歩引いて一般的なパフォーマンス パターンを特定してみましょう。

パフォーマンス パターン

最もシンプルなページは HTML マークアップのみで構成されています。CSS、JavaScript、その他のリソースは不要ですこのページをレンダリングするには、ブラウザでリクエストを開始し、HTML ドキュメントが届くのを待ってから解析し、DOM を構築して、最後に画面にレンダリングする必要があります。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試してみる

Hello World CRP

T0 と T1 の間の時間は、ネットワークとサーバーの処理時間をキャプチャします。最も良いケース(HTML ファイルが小さい場合)では、1 回のネットワーク ラウンド トリップでドキュメント全体を取得します。ファイルが大きい場合は、TCP 転送プロトコルの仕組み上、必要なラウンドトリップ数が増える可能性があります。そのため、上のページのクリティカル レンダリング パスは、最良であれば 1 往復(最低)となります。

では、同じページで、外部 CSS ファイルを使用するケースを検討しましょう。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

試してみる

DOM と CSSOM の CRP

繰り返しになりますが、HTML ドキュメントを取得する際はネットワーク ラウンドトリップが発生し、取得したマークアップから CSS ファイルも必要であることがわかります。つまり、ブラウザは画面上にページをレンダリングするために、サーバーに戻って CSS を取得する必要があります。そのため、このページを表示するには少なくとも 2 回のラウンドトリップが発生します。繰り返しになりますが、CSS ファイルは複数回のラウンドトリップを必要とする場合があるため、「最小値」が強調されています。

ここでは、クリティカル レンダリング パスを説明する際に使用する用語をいくつか示します。

  • クリティカル リソース: ページの最初のレンダリングをブロックする可能性のあるリソース。
  • クリティカル パス長: ラウンドトリップ回数、またはすべてのクリティカル リソースを取得するために必要な合計時間。
  • クリティカル バイト数: ページの最初のレンダリングに必要なバイト数の合計。これは、すべてのクリティカル リソースの転送ファイルサイズの合計です。最初の例では、HTML ページに 1 つの重要なリソース(HTML ドキュメント)が 1 つ含まれていました。クリティカルパス長も 1 回のネットワーク・ラウンドトリップと等しく(ファイルが小さいと仮定)、総クリティカル・バイトは HTML 文書そのものの転送サイズだけでした。

これを、先ほどの HTML と CSS の例のクリティカル パス特性と比較してみましょう。

DOM + CSSOM CRP

  • 2 個の重要なリソース
  • 最小クリティカル パス長のラウンド トリップ数は 2 以上
  • 9 KB のクリティカル バイト

レンダリング ツリーを構築するには、HTML と CSS の両方が必要です。そのため、HTML と CSS の両方がクリティカル リソースとなります。CSS は、ブラウザが HTML ドキュメントを取得した後にのみ取得可能になるため、クリティカル パス長は、最低で 2 ラウンドトリップとなります。クリティカル バイトは合計 9 KB です。

次に、追加の JavaScript ファイルを追加します。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

試してみる

app.js を追加しました。これはページの外部 JavaScript アセットであり、パーサー ブロック リソース(クリティカル リソース)になります。さらに悪いことに、JavaScript ファイルを実行するには、ブロックして CSSOM を待つ必要があります。JavaScript は CSSOM にクエリを実行できるため、style.css をダウンロードして CSSOM を構築するまでブラウザは一時停止します。

DOM、CSSOM、JavaScript CRP

ところで、このページの「ネットワーク ウォーターフォール」を確認すると、CSS リクエストと JavaScript リクエストがほぼ同じタイミングで開始されていることがわかります。ブラウザは HTML を取得し、両方のリソースを発見して両方のリクエストを開始しています。その結果、前の画像に示すページのクリティカル パス特性は次のとおりです。

  • 3 個の重要なリソース
  • 最小クリティカル パス長のラウンド トリップ数は 2 以上
  • クリティカル バイト数は 11 KB

今回のクリティカル リソースは 3 つ、クリティカル バイトは合計で 11 KB です。ただし、CSS と JavaScript は同時に転送できるため、クリティカル パス長は変わらず 2 ラウンドトリップです。クリティカル レンダリング パスの特徴を把握すると、クリティカル リソースを特定し、ブラウザがリソースの取得をスケジューリングする方法を理解できるようになります。

サイト デベロッパーと会話したところ、ページに組み込まれている JavaScript はブロック不要であることがわかりました。スクリプト内のアナリティクスや他のコードは、ページのレンダリングをブロックする必要がないのです。それを踏まえて、async 属性を <script> 要素に追加して、パーサーのブロックを解除できます。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試してみる

DOM、CSSOM、非同期 JavaScript CRP

非同期スクリプトにはいくつかメリットがあります。

  • スクリプトがパーサー ブロックにならず、クリティカル レンダリング パスの一部ではなくなります。
  • 他にクリティカル スクリプトがないため、CSS が domContentLoaded イベントをブロックする必要もなくなります。
  • domContentLoaded イベントが発生するタイミングが早いほど、他のアプリ ロジックの実行も早く開始できます。

その結果、最適化済みのページは 2 つのクリティカル リソース(HTML と CSS)に戻りました。最小クリティカル パス長は 2 ラウンドトリップ、クリティカル バイトは合計 9 KB です。

最後に、CSS スタイルシートが印刷にのみ必要なケースではどうなるのか見てみましょう。

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

試してみる

DOM、非ブロック CSS、非同期 JavaScript CRP

style.css リソースは印刷にのみ使用されるため、ブラウザでは、ページをレンダリングする際に CSS をブロックする必要がありません。したがって、DOM 構築が完了した時点で、ブラウザにはページのレンダリングに必要な情報がすべてそろっています。その結果、このページのクリティカル リソースは 1 つ(HTML ドキュメント)、最小クリティカル レンダリング パス長は 1 ラウンドトリップになります。

フィードバック