2023年7月24日
フロントエンドパフォーマンスのチェックリスト2021年版(PDF、Apple Pages、MS Word)-後編
本記事は、原著者の許諾のもとに翻訳・掲載しております。
目次#
前編
- 準備段階:計画と指標
パフォーマンスを重視する文化、Core Web Vitals、パフォーマンスのプロファイル、CrUX、Lighthouse、FID、TTI、CLS、端末。 - 現実的な目標の設定
パフォーマンスバジェット、パフォーマンス目標、RAILフレームワーク、170KB/30KBバジェット。 - 環境の定義
フレームワークの選択、パフォーマンスコストの基準設定、Webpack、依存関係、CDN、フロントエンドアーキテクチャ、CSR、SSR、CSR + SSR、静的レンダリング、プリレンダリング、PRPLパターン。
中編
- アセットの最適化
Brotli、AVIF、WebP、レスポンシブ画像、AV1、アダプティブメディア読み込み、動画圧縮、Webフォント、Googleフォント。 - ビルドの最適化
JavaScriptモジュール、モジュール/ノーモジュールのパターン、ツリーシェイキング、コード分割、スコープホイスティング、Webpack、デファレンシャルサービング、Webワーカー、WebAssembly、JavaScriptバンドル、React、SPA、パーシャルハイドレーション、インポート・オン・インタラクション、サードパーティ、キャッシュ
後編
- デリバリの最適化
遅延読み込み、インターセクションオブザーバー、遅延レンダリングとデコーディング、クリティカルCSS、ストリーミング、リソースヒント、レイアウトシフト、サービスワーカー。 - ネットワーク接続、HTTP/2、HTTP/3
OCSPステープリング、EV/DV証明書、パッケージング、IPv6、QUIC、HTTP/3。 - テストとモニタリング
監査ワークフロー、プロキシブラウザ、404ページ、GDPRに基づくcookie同意プロンプト、パフォーマンス診断CSS、アクセシビリティ。 - 手軽に成果を上げる
- チェックリストのダウンロード(PDF、Apple Pages、MS Word)
- さあ、始めよう!
(チェックリストのPDFファイル(166KB)、編集可能なApple Pagesファイル(275KB)、.docxファイル(151KB)も自由にダウンロードできます。皆さんが最適化を楽しめますように!)
デリバリの最適化 #
45. クリティカルなJavaScriptの非同期読み込みにdeferを使用するか?
ユーザがページをリクエストするとき、ブラウザはHTMLを取得し、DOMを構築し、それからCSSを取得してCSSOMを構築し、さらにDOMとCSSOMを照合することによってレンダリングツリーを生成します。JavaScriptを解決する必要がある場合、ブラウザは解読が終わるまでページのレンダリングを開始しない
ため、レンダリングが遅延します。開発者はブラウザに対して、待機せずにページのレンダリングを開始するよう明確に指示する必要があります。これをスクリプト向けに実現する方法は、HTMLのdefer
とasync
属性を使用することです。
実際には、async
ではなくdefer
を使用するほうが良いことが分かっています。それでは、両者の違いとは何でしょうか。Steve Soudersによれば、async
スクリプトが登場すると、スクリプトは準備ができた段階ですぐに実行されます。このスクリプトがとても速く実行される場合、例えばスクリプトがキャッシュにすでに存在する場合は、実際にはスクリプトがHTMLパーサをブロックする可能性があります。defer
では、ブラウザはHTMLがパースされるまでスクリプトを実行しません。ですから、レンダリングの開始前にJavaScriptを実行する必要がなければ、defer
を使用するほうが良いでしょう。また、複数の非同期ファイルが実行される順番は確定的ではありません。
注目すべき点として、async
とdefer
はいくつかの面で誤解されています。最も重要なのは、async
とは、スクリプトの準備ができた時点でいつでもコードを実行するという意味ではないということです。asyncが意味するのは、スクリプトの準備ができており、なおかつ先行する同期作業が全て終了した時点で、いつでもスクリプトを実行するということです。Harry Robertsの言葉を借りれば、「async
スクリプトを同期スクリプトの後に設定した場合、async
スクリプトは、最も遅い同期スクリプトと同じ速さにしかなりません」。
また、async
とdefer
を両方使用することは推奨されません。モダンなブラウザはどちらもサポートしていますが、いずれの属性も使用されている場合、いつでもasync
が優先されます。
このテーマについてもっと詳しく知りたいならば、Milica Mihajlijaが執筆したBuilding the DOM fasterをご覧ください。このとても詳しいガイドは、投機的パーシング、async、deferの詳細について取り上げています。
46. コストが大きいコンポーネントをインターセクションオブザーバーとプライオリティヒントで遅延読み込みする。
一般に、重いJavaScript、動画、iframe、ウィジェット、場合によっては画像など、コストが大きいコンポーネントは全て遅延読み込みすることが推奨されます。画像とiframeのネイティブ遅延読み込みは、すでにloading
属性によって利用可能となっています(Chromiumのみ)。この属性は、リソースがビューポートからの計算上の距離に達するまで、そのリソースの読み込みを内部で遅延させます。
<!-- Lazy loading for images, iframes, scripts.
Probably for images outside of the viewport. -->
<img loading="lazy" ... />
<iframe loading="lazy" ... />
<!-- Prompt an early download of an asset.For critical images, e.g. hero images. -->
<img loading="eager" ... />
<iframe loading="eager" ... />
この基準となる距離は、取得される画像リソースの種類や有効な接続のタイプなど、いくつかの項目に依存します。しかし、ChromeのAndroid版を使用して行われた実験は、4G接続の場合、遅延読み込みされる画面外の画像の97.5%が表示後10ミリ秒以内に完全に読み込まれたことを示しているため、問題はないでしょう。
また、<script>
、<img>
、<link>
要素のimportance属性(high
またはlow
)を使用することもできます(Blinkのみ)。実際、これはカルーセルの画像の優先順位を下げたり、スクリプトの優先順位をつけ直したりするための優れた手段となります。しかし、場合によっては、もっときめ細かいコントロールが必要になるでしょう。
<!--
When the browser assigns "High" priority to an image,
but we don’t actually want that.
-->
<img src="less-important-image.svg" importance="low" ... />
<!--
We want to initiate an early fetch for a resource,
but also deprioritize it.
-->
<link rel="preload" importance="low" href="/script.js" as="script" />
もう少し洗練された遅延読み込みの方法のなかで、最もパフォーマンスが高いのは、インターセクションオブザーバーAPIを使用することです。このAPIは、ターゲットとする要素と、その祖先要素または最上位ドキュメントのビューポートとの交差点における変化を非同期的に監視する方法を提供します。基本的に、新しいIntersectionObserver
オブジェクトを作成する必要があり、それがコールバック関数と一連のオプションを受け取ります。その後、観測対象のターゲットを追加します。
コールバック関数は、ターゲットが表示されるときか非表示となるときに実行されます。そのため、関数がビューポートに割り込むときに、要素が表示される前に何らかの行動を開始することができます。実際、rootMargin
(ルート周辺の余白)とthreshold
(目標とするターゲットの表示割合を示す数字やその範囲)を使用することで、オブザーバのコールバックをいつ呼び出すかを細かくコントロールすることが可能です。
Alejandro Garcia Angladaは、インターセクションオブザーバーを実際に導入する方法に関する便利なチュートリアルを公表しています。Rahul Nanwaniは、前景と背景の画像の遅延読み込みに関して詳しい記事を執筆しています。Google Fundamentalsも、インターセクションオブザーバーによる画像と動画の遅延読み込みに関する詳細なチュートリアルを提供しています。
皆さんはアート主体のストーリーテリングを覚えているでしょうか。この形式のストーリーテリングは長大で、オブジェクトは移動するものもあれば、固定されているものもあります。インターセクションオブザーバーでは、高パフォーマンスのスクローリーテリング(スクロールと連動したストーリーテリング)を実装することもできます。
他にも遅延読み込みが可能なものはないか、再度チェックしましょう。翻訳文字列や絵文字の遅延読み込みも助けになるかもしれません。Mobile Twitterは、これによって、新たなインターナショナリゼーションパイプラインのJavaScript実行時間を80%高速化しました。
ただし、注意すべき点もあります。遅延読み込みは、原則というよりは例外であるべきです。実際にはすぐに表示したいものを遅延読み込みするのは、おそらく合理的ではないでしょう。例えば、製品ページの画像、ヒーロー画像、メインナビゲーションをインタラクティブにするために必要なスクリプトなどが挙げられます。
距離の基準値は最近、高速な接続(4Gなど)では3000pxから1250pxに、低速な接続(3Gなど)では4000pxから2500pxに縮小しました。(プレビューを拡大)
翻訳文字列の遅延読み込みによって、Mobile Twitterは新たなインターナショナリゼーションパイプラインのJavaScript実行時間を80%高速化しました。(画像の出典:Addy Osmani)(プレビューを拡大)
47. 画像を段階的に読み込む。
画像の段階的な読み込みをページに追加することで、遅延読み込みをさらに一段上のレベルに引き上げられるかもしれません。Facebook、Pinterest、Medium、Woltと同様に、最初は低品質な画像やぼやけた画像を読み込み、ページの読み込みが進むにつれて、BlurHashテクニックやLQIP(Low Quality Image Placeholders)テクニックを使用して当初の画像を完全な品質のバージョンに置き換えることができます。
こうしたテクニックがユーザ体験を改善するかどうかについての意見はさまざまですが、First Contentful Paintを改善するのは間違いありません。また、SVGプレースホルダとして画像の低品質バージョンを作成するSQIPや、CSS線形勾配を持つGradient Image Placeholdersの使用によって、上記を自動化することも可能です。
これらのプレースホルダは、テキストと同様の圧縮方法によって自然と十分に圧縮されるため、HTMLに埋め込むことができます。Dean Humeの記事では、このテクニックをインターセクションオブザーバーによって実装する方法が解説されています。
フォールバックはどうすべきでしょうか。ブラウザがインターセクションオブザーバーをサポートしていない場合でも、ポリフィルを遅延読み込みしたり、画像を即座に読み込んだりすることができます。そのためのライブラリもあります。
もっとしゃれたテクニックが好きな方には、こんな方法もあります。画像をトレースし、単純な図形と線を使用して軽いSVGプレースホルダを作成し、それを最初に読み込んだ後、プレースホルダのベクター画像から(読み込み済みの)ビットマップ画像に移行するのです。
José M. PérezによるSVG遅延読み込みのテクニック。(プレビューを拡大)
48. content-visibility
でレンダリングを遅延させているか?
大量のコンテンツブロック、画像、動画が含まれる複雑なレイアウトでは、データのデコードとピクセルのレンダリングはコストがとても大きい作業であるかもしれません。特にローエンドの端末ではなおさらです。content-visibility: auto
を使用すると、コンテナがビューポートの外にある間は、ブラウザに子のレイアウトをスキップさせることができます。
例えば、最初の読み込みでは、フッタや後半のセクションのレンダリングはスキップしたほうが良いかもしれません。
footer {
content-visibility: auto;
contain-intrinsic-size: 1000px;
/* 1000px is an estimated height for sections that are not rendered yet. */
}
content-visibility: auto;の挙動はoverflow: hidden;と似ています
が、デフォルトのmargin-left: auto;
、margin-right: auto;
、widthの宣言の代わりにpadding-left
とpadding-right
を適用すれば修正できます。パディングは基本的に、要素がコンテンツボックスからあふれても、全体がボックスモデルの外に出たり、切り取られたりすることなく、パディングボックスに入るようにすることを可能にします。
新たなコンテンツが最終的にレンダリングされたときは、一定のCumulative Layout Shift(CLS)が発生している可能性に注意しましょう。ですから、 適切なサイズのプレースホルダとcontain-intrinsic-size
を使用するのは良いアイデアと言えます(Unaに感謝します)。
Thijs Terluinは、上記の2つのプロパティに関するはるかに詳しい内容や、contain-intrinsic-size
がブラウザによってどのように計算されるかに関する記事を公表しています。Malte Ublは、読者の方にも可能な計算方法を解説しています。JakeとSurmaによる短い解説動画ではプロパティの仕組みを説明しています。
もう少し細かい対応が必要なら、CSS Containmentを利用しましょう。これにより、他の要素のサイズ、位置調整や計算済みのスタイルのみが必要である場合、あるいは要素が現在オフキャンバスである場合に、DOMノードの子孫のレイアウト、スタイル、描画作業を手動でスキップできます。
デモでは、チャンク化したコンテンツエリアにcontent-visibility: auto
を適用することで、最初の読み込みのレンダリングのパフォーマンスが7倍改善されています。(プレビューを拡大)
49. decoding="async"でデコードを遅延させているか?
コンテンツが画面外で表示されていても、顧客が必要なときは利用できるようにしたい場合もあります。クリティカルパス上のどんなものもブロックせず、非同期でデコードやレンダリングが行われるのが理想です。decoding="async"
を使用すれば、ブラウザが画像をメインスレッド外でデコードする許可を与えることができます。これにより、画像のデコードによるCPU使用時間に関して、ユーザへの影響を回避することが可能です(Malte Ublより)。
<img decoding="async" … />
別の選択肢として、画面外の画像については、最初にプレースホルダを表示し、その画像がビューポート内に表示されたときに、インターセクションオブザーバーを使用して画像がバックグラウンドでダウンロードされるようにネットワーク呼び出しをトリガーするという方法もあります。また、img.decode()によってデコードまでレンダリングを遅延させることや、Image Decode APIが利用できない場合は画像をダウンロードすることが可能です。
画像をレンダリングするときは、例えばフェードインアニメーションを使用することができます。Katie HempeniusとAddy Osmaniは、Speed at Scale: Web Performance Tips and Tricks from the Trenchesという講演で、さらなる知見を共有しています。
50. クリティカルCSSを生成・提供しているか?
ブラウザに可能な限り早くページのレンダリングを開始させるには、ページの最初に表示される部分のレンダリング開始に必要な全てのCSS(通称「クリティカルCSS」や「アバブ・ザ・フォールドCSS」)を収集し、それをページの<head>
内に埋め込むことにより、ラウンドトリップを減らすのが一般的な方法となっています。スロースタートフェーズではやり取りできるパッケージの容量が限られているため、クリティカルCSSのバジェットは約14KBとなります。
それ以上の容量を求めるなら、ブラウザが取得するスタイルを増やすための追加的なラウンドトリップが必要です。CriticalCSSとCriticalは、使用するあらゆるテンプレートのクリティカルCSSの出力を可能にします。しかし、私たちの経験では、過去のどんな自動システムも、全てのテンプレートのクリティカルCSSを手作業で収集することに負けています。実際、私たちは最近、手作業によるアプローチに回帰しました。
収集後はWebpackプラグインのcrittersを使用して、クリティカルCSSを埋め込み、残りを遅延読み込みします。可能であれば、Filament Groupが採用している条件付きの埋め込みアプローチの使用を検討しましょう。あるいは、埋め込みコードを静的アセットにオンザフライで変換しましょう。
loadCSSなどのライブラリでCSS全体を非同期で読み込むことは、実際には必要ありません。media="print"
を使用することで、ブラウザをだましてCSSを非同期で取得させつつ、読み込み後のCSSを画面環境に適用することができます(Scottに感謝します)。
<!-- Via Scott Jehl. https://www.filamentgroup.com/lab/load-css-simpler/ -->
<!-- Load CSS asynchronously, with low priority -->
<link rel="stylesheet"
href="full.css"
media="print"
onload="this.media='all'" />
各テンプレートの全てのクリティカルCSSを収集するときは、「アバブ・ザ・フォールド」(スクロールしなくても見える範囲)のみを探索するのが一般的です。しかし、複雑なレイアウトでは、レイアウトの土台の部分を探索に含めるのが良いかもしれません。また、Core Web Vitalsスコアが低下しないように、膨大な再計算と再描画のコストを回避するのも賢明でしょう。
ユーザがページの中ほどに直接リンクするURLを取得したが、CSSがまだダウンロードされていなかった場合はどうなるでしょうか。この場合、例えば埋め込みCSSではopacity:0;
、完全なCSSファイルではopacity:1
を使用して、クリティカルではないコンテンツを隠し、CSSが利用可能になった時点で表示するのが一般的です。しかし、この方法の大きなデメリットとして、接続が遅いユーザはページのコンテンツをまったく読めない可能性があります。そのため、たとえ適切なスタイルではないとしても、コンテンツを常に表示し続けるほうが良いと言えます。
クリティカルCSS(と他の重要なアセット)をルートドメインの別個のファイルに設定することには、場合によっては埋め込みよりも、キャッシングの面でメリットがあります。Chromeはページをリクエストするときにルートドメインへの第2のHTTP接続を投機的に開くので、このCSSを取得するためのTCP接続の必要がなくなります。これは、一連のクリティカルCSSファイル(critical-homepage.css、critical-product-page.cssなど)を作成し、それを埋め込むことなくルートから提供できることを意味します(Philipに感謝します)。
注意すべき点もあります。HTTP/2では、クリティカルCSSは個別のCSSファイルに保存され、HTMLを膨張させることなくサーバプッシュ経由で提供できます。問題は、サーバプッシュには多くの落とし穴と複数のブラウザの競合条件があり、厄介だということです。サーバプッシュが一貫してサポートされていたことはなく、キャッシングに関する問題もありました(Hooman Beheshtiのプレゼンテーションのスライド114以降を参照)。
サーバプッシュの影響は、実際にはマイナスとなり、ネットワークのバッファが膨張することでドキュメントの真のフレームの提供が妨げられる可能性があったのです。ですから、Chromeがここ最近、サーバプッシュをサポート対象から除外することを計画しているのは、あまり驚きではありませんでした。
51. CSSルールの再グループ化を試す。
私たちはクリティカルCSSに慣れ親しんでいますが、それを超える可能性がある最適化も存在します。Harry Robertsが実施した素晴らしい調査は、とても驚くべき結果を明らかにしました。例えば、メインCSSファイルを個別のメディアクエリに分割するのは良いアイデアかもしれません。これにより、ブラウザは優先順位の高いクリティカルCSSを取得し、それ以外の優先順位が低いものは全てクリティカルパスから完全に除外されます。
また、async
スニペットの前に<link rel="stylesheet" />
を設置しないようにしましょう。スクリプトがスタイルシートに依存しない場合、ブロックとなるスタイルの上に、ブロックとなるスクリプトを設置することを検討しましょう。依存する場合は、そのJavaScriptを2つに分割し、CSSの両側で読み込みます。
Scott Jehlは、埋め込まれたCSSファイルをサービスワーカーでキャッシュすることにより、別の興味深い問題を解決しました。この問題は、クリティカルCSSを使用している人にとっては共通のよくある問題です。その解決策とは、基本的にはstyle
要素にID属性を追加し、JavaScriptを使用して要素を発見しやすくすることです。その後、小規模なJavaScriptがそのCSSを発見し、Cache APIを使用してCSSをローカルブラウザのキャッシュに保存し(コンテンツタイプはtext/css
)、その後のページで使えるようにします。その後のページでの埋め込みを避け、代わりにキャッシュされたアセットを外部から参照するために、サイトへの最初のアクセス時にcookieを設定します。これで完成です。
注目すべき点として、動的なスタイル設定はコストが大きくなる可能性がありますが、それは通常、何百もの合成コンポーネントを同時にレンダリングし、そのコンポーネントに依存する場合に限られます。ですから、CSS-in-JSを使用している場合は、CSSがテーマやプロップに依存していないときに、そのCSS-in-JSライブラリが実行を最適化するようにしましょう。また、styled-componentsを合成し過ぎないようにすべきです。Aggelos Arvanitakisは、CSS-in-JSのパフォーマンスコストに関してさらなる知見を紹介しています。
52. 応答をストリームするか?
これは忘れられたり、無視されたりしていることが多いのですが、ストリームは非同期のデータのチャンクを読んだり書き込んだりするためのインタフェースを提供します。こうしたチャンクのうち、任意の時点にメモリ内で利用できるのは一部に限られる可能性があります。これらは基本的に、当初のリクエストを実施したページが、最初のチャンクが利用可能となった時点ですぐに応答の処理を開始できるようにするものです。また、ストリーミングに最適化されたパーサを使用して、コンテンツを段階的に表示できます。
複数のソースから1つのストリームを作成することが可能です。例えば、空のUIシェルを提供してJavaScriptでデータを入力する代わりに、シェルをキャッシュから、本文をネットワークから提供し、サービスワーカーにストリームを構築させることができます。Jeff Posnickが述べているように、CMSが部分的なテンプレートをまとめることでHTMLをサーバレンダリングし、それによってWebアプリケーションが動いている場合、そのモデルは応答のストリーミングを使用するモデルへ直接転換することが可能です。このケースでは、テンプレートのロジックはサーバではなくサービスワーカーで再現されます。Jake ArchibaldのThe Year of Web Streamsという記事は、この仕組みを構築するための厳密な方法を明らかにしています。これはパフォーマンスの向上にとても顕著な効果があります。
HTML応答を全てストリーミングすることの重要な強みの1つは、最初のナビゲーションリクエストの間にレンダリングされたHTMLが、ブラウザのストリーミングHTMLパーサのメリットをフル活用できることです。ページの読み込み後にドキュメントに挿入されるHTMLのチャンクは、(JavaScriptを通じて読み込まれるコンテンツと同様に)この最適化によるメリットを活用できません。
ブラウザのサポート状況はどうでしょうか。現在はまだ道半ばで、Chrome、Firefox、Safari、EdgeはこのAPIを部分的にのみサポートしています。一方、サービスワーカーは全てのモダンなブラウザでサポートされています。大胆なことがしたくなったら、ストリーミング応答の試験的な実装をチェックしてみても良いでしょう。これは、まだ本文を生成している間にリクエスト送信を開始することを可能にします。この機能はChrome 85で利用可能です。
Cloudinaryの調査によれば、世界のAndroid Chromeユーザの18%はライトモード(省データモード)を有効にしています。(プレビューを拡大)
53. 接続状況に対応したコンポーネントの導入を検討する。
データのコストは重くなることがあります。ペイロードが大きくなるにつれて、サイトやアプリケーションにアクセスする際に省データモードを選択するユーザに配慮する必要が出てきます。省データクライアントヒントのリクエストヘッダは、コストやパフォーマンスの制約があるユーザに合わせて、アプリケーションとペイロードをカスタマイズすることを可能にします。
実際、高DPI画像を低DPI画像にするためのリクエストの書き換え、Webフォントの削除、手の込んだパララックス効果の削除、サムネイルと無限スクロールのプレビュー、動画の自動再生の停止、サーバプッシュ、表示項目の数の削減、画像の品質の引き下げに加え、マークアップを提供する方法の変更さえ可能です。Tim Vereeckeは、データを節約(削減)する戦略についてのとても詳しい記事を公開し、省データのための数多くの選択肢を紹介しています。
誰がsave-data
を使用しているのだろうと思う読者もいるかもしれません。世界のAndroid Chromeユーザの18%はライトモードを有効にしており(つまりSave-Data
をオンにしており)、さらに実際の人数はこれを上回る可能性が高いと言えます。Simon Hearneの調査によれば、ライトモードの選択率が最も高いのは比較的安い端末ですが、多くの例外が存在します。例えば、カナダのユーザの選択率は34%超(米国では約7%)で、Samsungの最新の旗艦モデルのユーザによる選択率は世界全体で約18%となっています。
Save-Data
モードがオンになっていると、Chrome Mobileはユーザ体験を最適化して提供します。つまり、font-display: swap
と遅延読み込みを強制的に適用し、遅延スクリプトを使用した代替的なWeb体験です。こうしたブラウザの最適化に頼るよりも、自分でユーザ体験を構築するほうが理にかなっています。
このヘッダは現在Chromiumのみでサポートされており、ChromeのAndroid版か、デスクトップ端末のData Saver拡張機能を通じて提供されています。最後に、ネットワークの種類に応じて、コストが大きいJavaScriptモジュールや高解像度の画像と動画を提供するには、Network Information APIも使用することができます。Network Information API、特にnavigator.connection.effectiveType
は、RTT
、downlink、effectiveType
の値(とその他の少数の項目)を使用して、ユーザが処理できる接続とデータを表示します。
これに関連して、Max Böckは接続状況に対応したコンポーネントについて、Addy Osmaniはアダプティブなモジュール提供について語っています。例えばReactでは、異なる接続の種類に応じて、異なる方式でレンダリングされるコンポーネントを書くことができます。Maxが示しているとおり、ニュース記事の
Offline
:alt
テキスト付きのプレースホルダ2G / save-dataモード
:低解像度の画像- Retinaではないディスプレイの
3G
:中解像度の画像 - Retinaディスプレイの
3G
:高解像度のRetina画像 4G
:HD動画
Dean Humeは、サービスワーカーを使用した同様のロジックの現実的な実装方法を解説しています。動画については、デフォルトでは動画ポスターを表示し、接続が良い場合は「再生」アイコン、動画プレーヤのシェル、動画のメタデータなどを表示することができます。上記をサポートしていないブラウザ向けのフォールバックとして、canplaythrough
イベントをリッスンし、Promise.race()
を使用することで、canplaythrough
イベントが2秒以内に発生しない場合にソース読み込みをタイムアウトすることが可能です。
もう少し詳しく知りたい方向けに、スタートに適したリソースをいくつか紹介します。
- Addy OsmaniはReactでアダプティブなデータ提供を実装する方法を示しています。
- React Adaptive Loading Hooks & UtilitiesはReact向けのコードスニペットを提供します。
- Netanel BasalはAngularにおける接続状況に対応したコンポーネントについて追求しています。
- Theodore VorillasはVueでNetwork Information APIを使用してアダプティブコンポーネントを提供する仕組みについて解説しています。
- Umar Hansaはコストが大きいJavaScriptを選択的にダウンロード/実行する方法を説明しています。
54. 端末のメモリに対応したコンポーネントの導入を検討する。
しかし、ネットワークの接続からユーザについて知ることができる情報はほんの一面に過ぎません。一歩踏み込んで、Device Memory APIで、利用できる端末メモリに基づいてリソースを動的に調整することも可能です。navigator.deviceMemory
は、端末のRAMをギガバイト単位(2のべき乗未満は切り捨て)で返します。このAPIには、同じ値を報告するDevice-Memory
というクライアントヒントヘッダも搭載されています。
おまけ:Umar Hansaは、動的インポートによってコストが大きいスクリプトを遅延させる方法を解説しています。これにより、端末のメモリ、ネットワークの接続性、ハードウエアの並行処理能力に基づいてユーザ体験を変えることが可能になります。
Chrome 46以降のBlinkにおけるさまざまなリソースの優先順位の内訳。(画像の出典:Addy Osmani)(プレビューを拡大)
55. デリバリを高速化するために接続をウォームアップする。
リソースヒントを使用して時間を節約しましょう。例えば、dns-prefetch
(バックグラウンドでDNSルックアップを実行する)、preconnect
(ブラウザに対してバックグラウンドで接続のためのハンドシェイク(DNS、TCP、TLS)を開始するように要求する)、prefetch
(ブラウザに対してリソースをリクエストするように要求する)、preload
(リソースを実行することなく先読みするなど)といったリソースヒントがあります。モダンなブラウザのサポートも充実しており、間もなくFirefoxでもサポートされる見込みです。
皆さんはprerender
を覚えていますか。これは、次のナビゲーションのために、ブラウザにバックグラウンドでページ全体をビルドさせるのに使用されていたリソースヒントです。その実装には、膨大なメモリフットプリントと帯域幅の使用から、アナリティクスヒットと広告インプレッションが複数回カウントされることに至るまで、大きな問題がありました。
当然、非推奨となりましたが、ChromeチームはこれをNoState Prefetchの仕組みとして復活させました。実際、Chromeはprerender
ヒントをNoState Prefetchの代わりとして扱っているため、現在もまだ使用することができます。Katie Hempeniusが上記の記事で説明しているとおり、「prerenderingと同様に、NoState Prefetchはリソースを事前に取得します。ただし、prerenderingとは異なり、事前にJavaScriptを実行することや、ページの一部をレンダリングすることはありません」。
NoState Prefetchは約45MiBのメモリしか使用せず、取得されるサブリソースの取得時のネットプライオリティはIDLE
となります。Chrome 69以降、NoState Prefetchは全てのリクエストにPurpose: Prefetchヘッダを追加しています。これは全てのリクエストを通常のブラウジングから区別するためのものです。
プリレンダリングの別の選択肢とportalsにも注目しましょう。portalsはプライバシーに配慮したプリレンダリングの新たな取り組みで、シームレスなナビゲーションのためにコンテンツのinsetのpreview
を提供します。
リソースヒントの使用は、パフォーマンスを向上させる最も簡単な方法でしょう。実際、リソースヒントは有効です。それでは、何をいつ使用すれば良いのでしょうか。Addy Osmaniが説明しているとおり、現在のページや、複数のナビゲーションの境界をまたいで将来のナビゲーションに利用される可能性が非常に高いことが分かっている場合(ユーザがまだアクセスしていないページに必要なWebpackバンドルなど)は、リソースを先読みするのが合理的です。
AddyのChromeの読み込みの優先順位に関する記事では、Chromeがリソースヒントを解釈する厳密な仕組みが説明されています。そのため、どのアセットがレンダリングにとってクリティカルであるかを決めれば、それに高い優先順位を割り当てることができます。リクエストがどのように優先順位づけされているかを確認するには、Chrome DevToolsのネットワークリクエスト表の「priority」欄を有効化します(Safariでも同様です)。
現在では、大体の場合は少なくともpreconnect
とdns-prefetch
を使用することになるでしょう。prefetch
、preload
、prerender
を使用するときは注意しなければなりません。気をつけるべき点として、preconnect
とdns-prefetch
を使用しても、ブラウザが並行的にルックアップ/接続できるホストの数には限界があります。ですから、優先順位に基づいて指示を行うのが賢明です(Philip Tellisに感謝します)。
フォントは通常、ページの重要なアセットであるため、場合によってはブラウザに対してpreload
でクリティカルなフォントをダウンロードすることを要求するのが良いアイデアとなります。ただし、フォントの先読みの優先順位は複雑であるため、実際にパフォーマンスに貢献しているかをダブルチェックすることが必要です。preload
は優先順位が高いとみなされるので、クリティカルCSSなどのさらに重要なリソースよりも優先される場合があります(Barryに感謝します)。
<!-- Loading two rendering-critical fonts, but not all their weights. -->
<!--
crossorigin="anonymous" is required due to CORS.
Without it, preloaded fonts will be ignored.
https://github.com/w3c/preload/issues/32
via https://twitter.com/iamakulov/status/1275790151642423303
-->
<link rel="preload" as="font"
href="Elena-Regular.woff2"
type="font/woff2"
crossorigin="anonymous"
media="only screen and (min-width: 48rem)" />
<link rel="preload" as="font"
href="Mija-Bold.woff2"
type="font/woff2"
crossorigin="anonymous"
media="only screen and (min-width: 48rem)" />
<link rel="preload">
はmedia
属性を許容するため、上記のように、@media
クエリのルールに基づいてリソースを選択的にダウンロードすることを選べます。
さらに、imagesrcset
属性とimagesizes
属性を使用して、発見されるのが遅いヒーロー画像や、JavaScriptを通じて読み込まれる画像(動画ポスターなど)の先読みを高速化することができます。
<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ -->
<link rel="preload" as="image"
href="poster.jpg"
imagesrcset="
poster_400px.jpg 400w,
poster_800px.jpg 800w,
poster_1600px.jpg 1600w"
imagesizes="50vw">
また、JSONをfetchとして先読みし、JavaScriptがリクエストを開始する前に発見することも可能です。
<!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ -->
<link rel="preload" as="fetch" href="foo.com/api/movies.json" crossorigin>
さらに、スクリプトの実行を実質的に遅延させるために、JavaScriptを動的に読み込むこともできます。
/* Adding a preload hint to the head */
var preload = document.createElement("link");
link.href = "myscript.js";
link.rel = "preload";
link.as = "script";
document.head.appendChild(link);
/* Injecting a script when we want it to execute */
var script = document.createElement("script");
script.src = "myscript.js";
document.body.appendChild(script);
注意すべき落とし穴もあります。preload
はアセットのダウンロード開始時間を当初のリクエストに近づけるのに適していますが、先読みされたアセットは、リクエストを実施したページとひもづいたメモリキャッシュに保存されます。preload
はHTTPキャッシュと相性が良く、アイテムがHTTPキャッシュにすでに存在する場合はネットワークリクエストが送信されません。
したがって、発見が遅いリソース、background-image
を通じて読み込まれるヒーロー画像、クリティカルなCSS(やJavaScript)の埋め込み、残りのCSS(やJavaScript)の先読みに便利です。
重要な画像を早く先読みしましょう。JavaScriptによる発見を待つ必要はありません。(画像の出典:“Preload Late-Discovered Hero Images Faster”(Addy Osmani著))(プレビューを拡大)
preload
タグは、ブラウザがサーバからHTMLを受け取り、先読みパーサがpreload
タグを発見した後にのみ、先読みを開始することができます。HTTPヘッダを通じた先読みは、ブラウザがリクエストを開始するためにHTMLをパースするのを待つ必要がないため、少し速くなる可能性があります(ただし、これは議論の対象となっています)。
Early Hintsはもっと役に立つでしょう。これはHTMLの応答ヘッダが送信される前から、先読みを開始することを可能にします(ChromiumとFirefoxのロードマップに掲載されています)。さらに、Priority Hintsはスクリプト読み込みの優先順位を示すのに役立ちます。
注意:preload
を使用する場合、as
を必ず定義しなければなりません。さもないと何も読み込まれないことになります。さらに、crossorigin
属性なしでフォントを先読みすると、フォントが二重に取得されます。prefetch
を使用している場合、FirefoxのAge
ヘッダの問題に気をつけましょう。
サービスワーカーを使用すれば、必要最低限のデータのみをリクエストし、そのデータを完全なHTML文書に変換してFCPを改善することができます。(Phil Waltonより)(プレビューを拡大)
56. キャッシングとネットワークのフォールバックとしてサービスワーカーを使用する。
どんなネットワーク上のパフォーマンス最適化も、ユーザのマシンにキャッシュをローカル保存するより速くはなりません(ただし、例外もあります)。WebサイトがHTTPS通信を利用している場合、サービスワーカーのキャッシュに静的アセットをキャッシングし、オフラインのフォールバックを(あるいはオフラインのページも)保存すれば、ネットワークに接続することなくこれらをユーザのマシンから取得できます。
Phil Waltonが提案するとおり、サービスワーカーでは、応答をプログラムで生成することによって、より小さいHTMLペイロードを送信できます。サービスワーカーは、サーバから必要最低限のデータ(HTMLコンテンツの一部、マークダウンファイル、JSONデータなど)のみをリクエストし、そのデータをプログラムで完全なHTML文書へ変換することが可能です。ユーザがサイトを1度訪問し、サービスワーカーがインストールされれば、ユーザは二度とHTMLページ全体をリクエストする必要がありません。これはパフォーマンスに素晴らしい影響を与える可能性があります。
ブラウザのサポート状況はどうでしょうか。サービスワーカーは広くサポートされており、どちらにせよネットワークがフォールバックとなります。サービスワーカーはパフォーマンスの改善に確かに貢献します。さらに、その貢献度は高まっています。例えば、Background Fetchでは、サービスワーカーを通じたバックグラウンドでのアップロード/ダウンロードも可能です。
サービスワーカーにはいくつもの用途があります。例えば、「オフライン用に保存」機能を実装する、壊れた画像を処理する、タブ間のメッセージングを導入する、あるいはリクエストの種類によって異なるキャッシング戦略を提供するといった使い方が可能です。一般によく見られる信頼性が高い戦略は、少数のクリティカルなページ(オフラインページ、フロントページ、その他の各ケースで重要なページなど)とともに、アプリケーションのシェルをサービスワーカーのキャッシュに保存するというものです。
ただし、注意すべき落とし穴もあります。サービスワーカーを導入している場合、Safariのレンジリクエストに気をつけなければなりません(Workboxをサービスワーカー向けに使用している場合、Workboxにはレンジリクエストモジュールが存在します)。ブラウザコンソールのDOMException: Quota exceeded.
エラーにつまずいたことがあるなら、GerardoのWhen 7KB equals 7MBという記事を見てみましょう。
Gerardoが書いているとおり、「プログレッシブなWebアプリケーションを開発していて、サービスワーカーがCDNから提供された静的アセットをキャッシュすることでキャッシュストレージが膨張しているなら、クロスオリジンのリソース向けに適切なCORS応答ヘッダが存在するようにすること、サービスワーカーで意図せず不透明な応答をキャッシュしないようにすること、crossorigin
属性を<img>
タグに追加してクロスオリジンの画像アセットをCORSモードにオプトインすることを確実にしましょう」。
サービスワーカーを導入するための素晴らしいリソースは数多く存在します。
- Service Worker Mindsetは、サービスワーカーが水面下で機能する仕組みや、サービスワーカーを構築するときに知っておくべきことを理解するのに役立ちます。
- Chris Ferdinandiは、サービスワーカーに関する素晴らしいシリーズ記事を提供しています。この記事は、オフラインのアプリケーションを作成する方法を説明するとともに、最近閲覧したページのオフライン保存から、サービスワーカーでキャッシュ内の項目の有効期限を設定する方法まで、さまざまなシナリオをカバーしています。
- Service Worker Pitfalls and Best Practicesという記事では、サービスワーカーの落とし穴とベストプラクティスに加え、サービスワーカーの適用範囲、登録の遅延、キャッシングについてのヒントを紹介しています。
- Ire Aderinokunの"Offline First" with Service Workerという素晴らしいシリーズ記事では、アプリケーションのシェルの事前キャッシングに関する戦略を説明しています。
- Service Worker: An Introductionは、リッチなオフライン体験、定期的なバックグラウンド同期、プッシュ通知にサービスワーカーを使用する方法に関する実用的なヒントを提供します。
- Jake ArchibaldによるOffline Cookbookは昔ながらの記事ですが、いつでも参考に値します。この記事では、自分でサービスワーカーを作るためのレシピが紹介されています。
- Workboxは、特にプログレッシブなWebアプリケーションを構築するために開発されたサービスワーカーのライブラリです。
57. サーバワーカーをA/BテストなどのためにCDN/エッジで実行しているか?
現在、私たちはサービスワーカーをクライアント側で実行することにすっかり慣れています。しかし、CDNがサービスワーカーをサーバに実装することで、エッジでもパフォーマンスを微調整するためにサービスワーカーを使用できる可能性があります。 例えば、A/Bテストにおいて、HTMLが異なるユーザ向けにコンテンツを変更する必要があるときに、CDNサーバでサービスワーカーを使用することでロジックを処理できるでしょう。また、HTML書き換えのストリームによって、Google Fontsを使用するサイトを高速化できます。
サービスワーカ導入の時系列。Web Almanacによれば、サービスワーカを登録しているページは全てのデスクトップページの0.87%にとどまっています。(プレビューを拡大)
58. レンダリングのパフォーマンスを最適化する。
アプリケーションが重いと、すぐに気づかれてしまいます。ですから、ページをスクロールするときや要素のアニメーションが動くときにラグが生じないようにし、1秒当たり60フレームのフレームレートを常に守らなければなりません。それが不可能な場合は、少なくとも1秒当たりのフレーム数を15~60の範囲内で一定にすることが望ましいと言えます。どの要素やプロパティが変化するかをブラウザに伝えるには、CSSのwill-changeを使用しましょう。
ユーザ体験を確認するときは、DevToolsで不必要な再描画をデバッグするようにします。
- ランタイムのレンダリングパフォーマンスを測定しましょう。測定結果を理解するための便利なヒントもチェックしてみてください。
- 出発点として、Paul Lewisによるブラウザレンダリングの最適化に関するUdacityの無料講義と、Georgy MarchukによるBrowser painting and considerations for web performanceという記事をチェックしてみてください。
- Firefox DevToolsで"More tools → Rendering → Paint Flashing"を選択し、Paint Flashingを有効にしましょう。
- React DevToolsでは、"Highlight updates"にチェックをつけ、"Record why each component rendered"を有効にしましょう。
- Why Did You Renderを使用すると、コンポーネントが再レンダリングされたときに変更を素早く通知してくれます。
Masonryレイアウトを使用している場合は、かなり近いうちにCSSグリッドのみでMasonryレイアウトを構築できるようになる可能性があることを覚えておきましょう。
このテーマについてもっと詳しく知りたいならば、Nolan Lawsonが記事でレイアウトのパフォーマンスを正確に測定するための技を紹介しており、Jason Millerも代わりとなるテクニックを提案しています。Sergey Chikuyonokも、適切なGPUアニメーションを作成するための短い記事を執筆しています。
ブラウザは、あまりコストをかけずに変形や透明度に関するアニメーションをつけることができます。CSS Triggersは、CSSが再レイアウトやリフローのきっかけになるかをチェックするのに便利です。(画像の出典:Addy Osmani)(プレビューを拡大)
注記:GPU合成レイヤの変更はコストがとても小さくなります。そのため、opacity
とtransform
による合成をトリガーするだけで済むなら、それで問題ありません。Anna Migasは、UIのレンダリングパフォーマンスのデバッグに関する講演で数多くの実用的なアドバイスを提供しています。DevToolsで描画のパフォーマンスをデバッグする方法を知るには、Umarの描画パフォーマンス監査に関する動画をチェックしましょう。
59. 体感的なパフォーマンスを最適化しているか?
コンポーネントがページに表示される順番や、どのようにアセットをブラウザに提供するかという戦略は重要ですが、体感的なパフォーマンスの役割も過小評価すべきではありません。この考え方は待つことの心理的な側面に着目したものです。基本的には、顧客に別のことをさせたり、顧客の注意を引きつけたりしている間に、他の処理を済ませるという手法を採用します。体感マネジメント、割り込みスタート、早期完了、許容度マネジメントといった要素が重要になります。
これらは何を意味するのでしょうか。アセットを読み込む間、常に顧客の一歩先を行くことを目指しましょう。それによって、水面下では多くのことが起こっていても、スムーズに感じられるユーザ体験を提供できるのです。顧客の注意を引きつけ続けるために、読み込みインジケータの代わりにスケルトンスクリーン(実装デモ)を試したり、トランジション/アニメーションを追加したりすることができます。これ以上最適化するものがない場合は、基本的にユーザ体験を実際より良く見せるという手もあります。
Kumar McMillanは、The Art of UI Skeletonsというケーススタディで、動的リスト、テキスト、最終画面をシミュレーションする方法や、Reactで「Skeleton思考」について検討する方法について、アイデアとテクニックを提供しています。
ただし、スケルトンスクリーンは実装前にテストすべきです。なぜなら、一部のテストは、スケルトンスクリーンのパフォーマンスが全ての指標で最低となる可能性があることを示しているからです。
60. レイアウトのシフトと再描画を防止しているか?
体感的なパフォーマンスに関して、特にうっとうしいと感じられるものの1つはレイアウトシフト(リフロー)でしょう。これは、画像と動画の寸法の変化、Webフォント、広告の挿入、あるいは発見されるのが遅いスクリプトによってコンポーネントに実際のコンテンツが読み込まれることが原因で発生します。その結果、顧客が記事を読み始めているにもかかわらず、読んでいる場所より上のレイアウトが移動することで邪魔されかねません。こうした体験は突然で、どこを読んでいるかまったく分からなくなることが多いものです。このような場合、読み込みの優先順位を検討し直す必要があるでしょう。
コミュニティでは、リフローを回避するためのテクニックや代替策が開発されています。一般的に、ユーザとのインタラクションによる応答以外では、既存のコンテンツの上に新しいコンテンツを挿入するのは避けるべきです。画像には常に[width属性とheight属性]を設定することで、モダンなブラウザがデフォルトでボックスを割り当て、スペースを確保できるようにしましょう(Firefox、Chrome)。
画像と動画の両方について、メディアが表示されるディスプレイボックスを確保するためにSVGプレースホルダを使用できます。これは、アスペクト比を維持する必要があるときも、その領域が適切に確保されることを意味します。また、レイアウトのスロットを事前に割り当てたり、広告や動的コンテンツのプレースホルダ(フォールバック画像)を使用したりすることも可能です。
外部スクリプトで画像を遅延読み込みする代わりに、ネイティブ遅延読み込みの使用を検討しましょう。ネイティブ遅延読み込みがサポートされていない場合に限り、外部スクリプトの読み込みにハイブリッド遅延読み込みを使用することも考えてみましょう。
すでに述べたとおり、Webフォントの再描画は常にグループ化し、全てのフォールバックフォントから全てのWebフォントへ一斉に移行しましょう。移行が唐突過ぎると感じさせないように、font-style-matcherを使用して、行の高さと間隔をフォント間で調整してください。
フォールバックフォントでWebフォントをエミュレートするためにフォントメトリックを上書きするには、@font-face記述子を使用できます(デモ、Chrome 87で有効)(ただし、複雑なフォントスタックでは複雑な調整が必要であることにご注意ください)。
CSSが遅い場合は、各テンプレートのヘッダに、レイアウトにとってクリティカルなCSSを埋め込みましょう。さらに、長いページの場合、縦のスクロールバーを追加するとメインコンテンツが16ピクセル左に移動します。スクロールバーを早く表示するには、html
にoverflow-y: scroll
を追加することで、スクロールバーを強制的に最初に描画できます。このテクニックが役立つ理由は、幅が変更され、アバブ・ザ・フォールドのコンテンツのリフローが生じると、スクロールバーが大きなレイアウトシフトを引き起こす可能性があるためです。ただし、このような事態が起きるのは、ほとんどはWindowsのようにオーバーレイではないスクロールバーを導入しているプラットフォームに限られます。なお、上記の仕組みはposition: stickyを無効にしてしまいます。なぜなら、これらの要素はスクロールによってコンテナの外に出ることがないためです。
スクロールページの上部に固定されているヘッダや、スティッキーなヘッダを扱う場合は、プレースホルダ要素やコンテンツの margin-top
などによって、そのヘッダを固定するためのスペースを確保しましょう。cookie同意バナーは例外で、CLSに影響しないはずですが、実装によっては影響を与えることがあります。このTwitterスレッドでは興味深い戦略とポイントが紹介されています。
さまざまな量のテキストが含まれる可能性があるタブコンポーネントについては、CSSグリッドのスタックによってレイアウトシフトを防ぐことができます。各タブのコンテンツを同じグリッドエリアに配置し、それらを一度に1つずつ隠すことで、コンテナが常により大きい要素の高さを取得し、レイアウトシフトが起こらないようにします。
もちろん、リストの下(フッタなど)にコンテンツがある場合は、無限のスクロールと「さらに読み込む」もレイアウトシフトを起こす可能性があります。CLSを改善するには、コンテンツを読み込むための十分なスペースを、ユーザがページのその部分までスクロールする前に確保しましょう。また、コンテンツの読み込みによって押し下げられる可能性があるページ下部のフッタやDOM要素を削除しましょう。さらに、画面外のコンテンツ向けのデータと画像を先読みし、ユーザがそこまでスクロールしたときにはすでにコンテンツが表示されているようにしてください。長いリストを最適化するには、react-windowなどのリスト仮想化ライブラリを使用できます(Addy Osmaniに感謝します)。
リフローの影響を確実に抑えるために、Layout Instability APIでレイアウトの安定性を測定しましょう。このAPIでは、Cumulative Layout Shift(CLS)スコアを計算し、それをテストの要件に含めることができます。そのため、スコアが悪化したときはいつでも追跡と修正が可能です。
ブラウザはレイアウトシフトのスコアを計算するうえで、ビューポートのサイズに加え、ビューポート内の不安定な要素が2つのレンダリングされたフレーム間でどれだけ移動したかに着目します。スコアは0
に近いのが理想です。Milica MihajlijaとPhilip Waltonは、CLSとその測定方法に関する素晴らしいガイドを公表しています。これは、特にビジネスに必要不可欠なタスクに関して、体感的なパフォーマンスを測定・維持し、混乱を回避するための良い出発点になります。
簡単なtips:何がレイアウトシフトの原因になっているかをDevToolsで発見するには、パフォーマンスパネルの"Experience"でレイアウトシフトについて調べてみましょう。
おまけ:リフローと再描画を減らしたいなら、Charis TheodoulouのDOMのリフロー/レイアウトスラッシングを最小限にするためのガイド、Paul Irishのレイアウト/リフローの要因のリスト、CSSTriggers.com(レイアウト、描画、合成をトリガーするCSSプロパティの参照表)をチェックしてみましょう。
ネットワーク接続とHTTP/2 #
61. OCSPステープリングは有効化されているか?
サーバのOCSPステープリングを有効化することでTLSハンドシェイクを高速化できます。オンライン証明書状態プロトコル(OCSP)は証明書失効リスト(CRL)プロトコルの代わりとして開発されました。いずれのプロトコルもSSL証明書が失効していないかを確認するために使用されます。
しかし、OCSPプロトコルはブラウザがリストをダウンロードして証明書情報を検索するのに時間を費やす必要がないため、ハンドシェイクに必要な時間が削減されます。
62. SSL証明書失効の影響を軽減しているか?
Simon Hearneは、"The Performance Cost of EV Certificates"という記事で、よく使われる証明書の概要に関する素晴らしい説明と、証明書の選択が全体的なパフォーマンスに及ぼす可能性がある影響に関する情報を提供しています。
Simonが書いているとおり、HTTPSの世界では、トラフィックの安全を確保するために複数の種類の証明書認証レベルが使用されています。
- ドメイン認証(DV)は、証明書の要求者がドメインを所有していることを認証します。
- 組織認証(OV)は、組織がドメインを所有していることを認証します。
- EV認証(EV)は、厳格な認証によって、組織がドメインを所有していることを認証します。
重要なのは、これらの証明書が全てテクノロジーの面では同一であるということです。違うのは証明書で提供される情報と属性だけです。
EV証明書は、人間が証明書をレビューし、その正当性を確認する必要があるため、高価で時間がかかります。一方、DV証明書は通常、無料で提供されます。例えばLet’s Encryptはオープンで自動化された証明書認証機関で、多くのホスティングプロバイダやCDNにうまく統合されています。実際、この記事の執筆時点で、Let’s Encryptは2億2500万件以上のWebサイト(PDF)に対応しています。ただし、これはページの2.69%(※訳注:現在原文で意図された期間の参照不可)を占めるに過ぎません(Firefoxで開いた場合)。
それでは、どのような問題があるのでしょうか。問題は、EV証明書は上記のOCSPステープリングを完全にはサポートしていないということです。ステープリングは、サーバが、証明書が失効していないことを証明書認証機関に確認し、その情報を証明書に追加(「ステープル」)することを可能にします。ステープリングがなければ、クライアントが全ての作業を実施しなければならず、TLSネゴシエーションの間に不必要なリクエストが生じます。接続状況が良くない場合、これは大きなパフォーマンスコスト(1000ミリ秒以上)を生み出す可能性があります。
EV証明書はWebパフォーマンスの面から見て優れた選択肢ではなく、DV証明書よりもはるかに大きな影響をパフォーマンスに与える可能性があります。最適なWebパフォーマンスのためには、OCSPステープリングに対応したDV証明書を常に提供しましょう。また、EV証明書に比べて大幅に安く、取得するのに労力もあまりかかりません。少なくとも、CRLiteが利用できるようになるまではそうでしょう。
圧縮が重要:圧縮されていない証明書チェーンの40~43%はサイズが大き過ぎて、1回のQUICフライトにおける3 UDPデータグラムの範囲に収まりません。(画像の出典:Fastly)(プレビューを拡大)
注記:QUICやHTTP/3の実用化が進むなかで、TLS証明書チェーンがQUICハンドシェイクのバイト容量の大きな部分を占める可変サイズのコンテンツであることは注目に値します。サイズは数百バイトから10KB超まで変動します。
サイズが大きい証明書は複数回のハンドシェイクを発生させるので、TLS証明書のサイズを小さく保つことはQUICやHTTP/3ではとても重要です。また証明書は確実に圧縮する必要があります。さもなければ、証明書チェーンのサイズが大き過ぎて1回のQUICフライトに収まらなくなってしまいます。
この問題と解決策については、はるかに詳しい情報やアドバイスが以下で紹介されています。
- EV Certificates Make The Web Slow and Unreliable(Aaron Peters著)
- The impact of SSL certificate revocation on web performance(Matt Hobbs著)
- The Performance Cost of EV Certificates(Simon Hearne著)
- Does the QUIC handshake require compression to be fast?(Patrick McManus著)
63. IPv6をすでに導入しているか?
IPv4アドレスは枯渇しつつあり、大規模なモバイルネットワークはIPv6を急速に導入しています(米国ではIPv6の導入率が50%の節目にほとんど到達しました)。そのため、将来に向けて万全に備えるために、DNSをIPv6にアップデートするのが賢明でしょう。ただし、ネットワーク全体でデュアルスタックがサポートされるようにしてください。デュアルスタックでは、IPv6とIPv4をお互いに共存させ、これらを同時に実行できます。結局、IPv6には後方互換性がないのです。また、調査によれば、IPv6は近隣探索プロトコル(NDP)と経路最適化によってWebサイトを10~15%高速化します。
64. 全てのアセットがHTTP/2(またはHTTP/3)で実行されるようにする。
Googleが過去数年間でより安全なHTTPS Webを推進していることを考えると、HTTP/2環境への移行に投資するのは間違いなく賢明です。実際、Web Almanacによれば、リクエスト全体の64%はすでにHTTP/2で実行されています。
HTTP/2が完璧ではなく、優先順位づけに問題があることを理解するのは重要です。しかし、HTTP/2はとても広くサポートされており、ほとんどの場合でHTTP/2の導入は改善につながります。
注意すべき点もあります。HTTP/2サーバプッシュはChromeから削除される予定であるため、実装がサーバプッシュに依存している場合は見直す必要があるでしょう。その代わりに、Early Hintsを導入すると良いかもしれません。Early Hintsは試験的機能としてすでにFastlyに統合されています。
まだHTTPを利用している場合、最も時間がかかるタスクは、まずHTTPSに移行し、それからHTTP/2の多重化と並列化に合わせてビルドプロセスを調整することでしょう。Bringing HTTP/2 to Gov.ukは、まさにこれを実現した素晴らしいケーススタディで、実現までの過程でCORS、SRI、WPTの課題を解決しています。本記事では今後、読者の皆さんがHTTP/2に移行中か、すでに移行しているという前提で話を進めます。
Web Almanacによれば、HTTP/2の正式な標準化からわずか4年で、2020年終わりにHTTP/2で送信されたリクエストの割合は全体の64%となりました。(画像の出典:Web Almanac)(プレビューを拡大)
65. HTTP/2を適切に導入する。
繰り返しになりますが、アセットをHTTP/2で提供する場合、これまでのアセットの提供方法を部分的に見直すことでメリットを得られる可能性があります。モジュールのパッケージ化と、多くの小さなモジュールの読み込みを並行し、両者のバランスをうまく保つことが必要です。しかし、結局は依然としてリクエストがまったくないことが最善であり、アセットの高速な初期デリバリとキャッシングの間でうまくバランスを取ることが目標になります。
一方、全てのアセットを連結するのは避け、代わりにインタフェース全体を多くの小さなモジュールに分割し、それをビルドプロセスの一環として圧縮し、並行して読み込みたい場合もあるかもしれません。それならば、1つのファイルを変更しても、スタイルシート全体やJavaScriptを再びダウンロードする必要はありません。パースにかかる時間も最小限となり、個々のページのペイロードを抑えられます。
一方、パッケージ化も依然として重要です。多くの小さいスクリプトを使用すると、全体的な圧縮にとって悪影響となり、キャッシュからオブジェクトを取得するコストが増加します。大きなパッケージを圧縮すると、ディクショナリを再利用できるというメリットがありますが、小さな個別のパッケージにはそのメリットがありません。この問題に対処するための標準化作業も行われていますが、今のところ実用化は遠い先になりそうです。第2に、ブラウザはこうしたワークフローについてまだ最適化されていません。例えば、Chromeはリソースの数に応じてプロセス間通信(IPC)をトリガーするため、何百ものリソースを利用するとブラウザのランタイムコストが生じます。
<head> </head>
<body>
<!-- HTTP/2 push this resource, or inline it, whichever's faster -->
<link rel="stylesheet" href="/site-header.css" />
<header>…</header>
<link rel="stylesheet" href="/article.css" />
<main>…</main>
<link rel="stylesheet" href="/comment.css" />
<section class="comments">…</section>
<link rel="stylesheet" href="/about-me.css" />
<section class="about-me">…</section>
<link rel="stylesheet" href="/site-footer.css" />
<footer>…</footer>
</body>
HTTP/2で最善の結果を達成するには、ChromeのJake Archibaldが提案しているとおり、CSSを段階的に読み込むことを検討しましょう。
それでも、CSSの段階的な読み込みを目指すことはできます。実際、bodyタグ内のCSSは、現在はChromeのレンダリングをブロックしません。ただし、優先順位づけに関する問題があるため、それほど単純ではありませんが、試してみる価値はあります。
HTTP/2接続の集約も有効かもしれません。これにより、HTTP/2のメリットを享受しつつ、ドメインシャーディングを行うことが可能です。ただし、現実に実行するのは困難で、一般的には優れた方法とは考えられていません。また、HTTP/2とサブリソース完全性は必ずしも相性が良くありません。
それでは、どうすれば良いのでしょうか。HTTP/2を利用しているなら、送信するパッケージを6~10個ほどにするのがまずまずの妥協点であると思われます(古いブラウザから見ても悪くありません)。実験と測定によって、自社のWebサイトにとってちょうど良いバランスを見つけましょう。
66. 全てのアセットを1回のHTTP/2接続で送信しているか?
HTTP/2の主な利点の1つは、1回の接続でアセットを送信できることです。しかし、例えばCORSに問題がある、crossorigin
属性を誤って設定するなどの失敗により、ブラウザが新たな接続を開始しなければならない場合もあります。
全てのリクエストを1回のHTTP/2接続で送信できているか、あるいは何かを誤って設定しているかを確認するには、DevTools → Networkで"Connection ID"欄を有効化しましょう。例えば、以下の図ではmanifest.jsonのみが個別の接続(451)を開始しており、それ以外の全てのリクエストは同じ接続(286)を共有しています。
manifest.jsonのみが個別の接続(451)を開始しており、それ以外の全てのリクエストは同じHTTP/2接続(286)を共有しています。iamakulovより。(プレビューを拡大)
67. サーバとCDNはHTTP/2をサポートしているか?
サーバやCDNによって、(依然として)HTTP/2のサポート状況は異なっています。選択肢をチェックするにはCDN Comparisonを使用しましょう。このサイトでは、サーバのパフォーマンスや、サポートが予想される機能をすぐに調べることができます。
Pat MeenanによるHTTP/2の優先順位に関する素晴らしい調査(動画)を参照し、サーバがHTTP/2の優先順位づけをサポートしているかテストしましょう。Patによれば、Linux 4.9以降のカーネルでHTTP/2の優先順位づけが高い信頼性で機能するには、BBR輻輳制御を有効化し、tcp_notsent_lowat
を16KBに設定することが推奨されます(Yoavに感謝します)。Andy Daviesは、複数のブラウザ、CDN、クラウドホスティングサービスでHTTP/2の優先順位づけに関する同様の調査を実施しました。
一方で、カーネルがTCP BBRをサポートしているかダブルチェックし、可能であれば有効化しましょう。TCP BBRは現在、Google Cloud Platform、Amazon Cloudfront、Linux(Ubuntuなど)で使用されています。
68. HPACK圧縮を利用しているか?
HTTP/2を使用している場合、不要なオーバーヘッドを削減できるように、サーバがHTTP応答ヘッダのHPACK圧縮を実装しているかをダブルチェックしましょう。一部のHTTP/2サーバはこうした仕様を完全にサポートしていないケースがあり、HPACKがその例となっています。H2specは(技術的にとても細かい内容であるものの)サポート状況をチェックするための素晴らしいツールです。HPACKの圧縮アルゴリズムはとても優れており、しかも有効です。
69. サーバのセキュリティーが盤石であることを確認する。
全てのブラウザによるHTTP/2の実装はTLSを利用しています。そのため、セキュリティー上の警告や、ページの一部の要素が機能しない状況は避けたい場合が多いでしょう。セキュリティヘッダが適切に設定されていることをダブルチェックし、既知の脆弱性を排除し、HTTPS設定を確認しましょう。
また、全ての外部プラグインと追跡スクリプトがHTTPSを通じて読み込まれていること、クロスサイトスクリプティングが不可能であること、HTTP Strict Transport SecurityヘッダとContent Security Policyヘッダの両方が適切に設定されていることを確かめてください。
70. サーバとCDNはHTTP/3をサポートしているか?
HTTP/2はいくつもの面でWebのパフォーマンスに重要な改善をもたらしましたが、まだ大きな改善の余地は残っています。特にTCPのヘッドオブラインブロッキングは、低速なネットワークでは顕著で、大きなパケットロスを生みます。HTTP/3はこうした問題を完全に解決しつつあります(記事)。
HTTP/2の問題に対処するために、IETFはGoogle、Akamaiなどとともに新たなプロトコルに取り組んでおり、その成果は最近、HTTP/3として標準化されました。
Robin MarxはHTTP/3について素晴らしい解説をしており、以下の説明は彼の解説に基づくものです。HTTP/3の本質は、機能の面ではHTTP/2にとてもよく似ていますが、内部の仕組みは大きく異なっています。HTTP/3はいくつもの面で改善されています。ハンドシェイクは高速化し、暗号化は改善され、独立したストリームの信頼性は向上し、暗号化とフローの制御も進歩しています。特に目立つ違いは、HTTP/3がトランスポート層としてQUICを使用しており、TCPではなくQUICパケットがUDPダイアグラムの上部に埋め込まれることです。
QUICはTLS 1.3をプロトコルに完全に統合しています。一方、TCPでは、TLS 1.3はプロトコルに上乗せされます。通常のTCPスタックでは、TCPとTLSがそれぞれ別個にハンドシェイクを行う必要があるため、数回分のラウンドトリップのオーバーヘッドが発生します。しかし、QUICでは両者を結合し、1回のみのラウンドトリップで完了することができます。TLS 1.3では今後の接続のための暗号化キーを設定できるため、2回目の接続以降は最初のラウンドトリップからすでにアプリケーション層のデータを送受信することができます。これは「0-RTT」と呼ばれます。
また、HTTP/2のヘッダ圧縮アルゴリズムは、優先順位づけのシステムとともに完全に書き直されました。さらにQUICは、各QUICパケットのヘッダの接続IDを通じて、Wi-Fiからセルラーネットワークへの接続のマイグレーションをサポートしています。ほとんどの実装は(TCPと同様に)カーネル空間ではなくユーザ空間で行われるため、プロトコルは今後、変化していくでしょう。
これらは全て大きな変化をもたらすのでしょうか。その答えはおそらくイエスです。特にモバイルの読み込み時間に影響をもたらすと思われますが、それだけではなく、エンドユーザへのアセットの提供方法にも影響する見込みです。HTTP/2では複数のリクエストが接続を共有しますが、HTTP/3のリクエストは接続を共有するだけでなく独立してストリームを送信するため、脱落したパケットが全てのリクエストに影響することはなくなり、1つのストリームのみに影響するようになります。
これは、1つの大きなJavaScriptバンドルでは、1つのストリームが停止したときにアセットの処理が減速しますが、複数のファイルストリームが並行的に送信される場合(HTTP/3)は影響が軽減されることを意味しています。そのため、パッケージ化は今も重要です。
HTTP/3への取り組みは依然として進行中です。Chrome、Firefox、Safariにはすでに実装されています。一部のCDNもQUICとHTTP/3をすでにサポートしています。2020年の終わりに、ChromeはHTTP/3とIETF QUICの導入を開始しました。実際、Googleの全てのサービス(Google Analytics、YouTubeなど)はすでにHTTP/3を利用しています。LiteSpeed Web ServerはHTTP/3をサポートしていますが、Apache、NGINX、IISはいずれもHTTP/3をまだサポートしていません。しかし、2021年にはこの状況が急速に変化するでしょう。
結論:サーバとCDNでHTTP/3を使用するという選択肢があるなら、おそらくそれを選ぶのが非常に賢明でしょう。主なメリットは、特にレイテンシが大きい接続において、複数のオブジェクトを同時に取得できることです。この分野ではあまり調査が行われていないため、確定的なことは分かりませんが、最初の調査は大きな期待が持てる結果となっています。
このプロトコルの仕様とメリットについてもっと詳しく知りたい方向けに、優れた出発点としてチェックすべき資料を紹介します。
- HTTP/3 Explainedは、HTTP/3とQUICプロトコルの文書化に向けた共同の取り組みの成果です。さまざまな言語で提供されており、PDFでも利用できます。
- Daniel StenbergはLeveling Up Web Performance With HTTP/3という動画を提供しています。
- Robin MarxのAn Academic’s Guide to QUICという講演では、QUICとHTTP/3プロトコルの基本的な概念を紹介し、HTTP/3がヘッドオブラインブロッキングと接続マイグレーションを処理する方法や、HTTP/3が有用性を保つためにどのように設計されているかについて説明しています(Simonに感謝します)。
- サーバがHTTP/3を利用しているかどうかはHTTP3Check.netで確認できます。
テストとモニタリング #
71. 監査ワークフローを最適化しているか?
適切な設定をすぐ利用できるように準備しておくことは、重要には思えないかもしれませんが、テストの時間をかなり削減できる可能性があります。WebPageTestのパブリックインスタンスにテストを送信するために、Tim KadlecのWebPageTest向けAlfred Workflowの使用を検討しましょう。実際、WebPageTestにはよく知られていない機能が数多くあります。ですから、時間を取ってWebPageTestのWaterfall Viewチャートを読む方法とWebPageTestのConnection Viewチャートを読む方法を学習し、パフォーマンスに関する問題を素早く診断・解決できるようにしましょう。
また、WebPageTestをGoogle Spreadsheetから実行することや、アクセシビリティ、パフォーマンス、SEOスコアをLighthouse CIでTravisの設定に組み込むこと、あるいはこれらをWebpackに直接組み込むことも可能です。
最近リリースされたAutoWebPerfにも注目しましょう。これは複数のソースからパフォーマンスデータを自動的に収集できるモジュラーツールです。例えば、クリティカルなページ上に毎日実行されるテストを設定し、CrUX APIからフィールドデータを、PageSpeed InsightsのLighthouseレポートからラボデータを取得できます。
また、何らかのデバッグを素早く実施する必要があるが、ビルドプロセスがとても遅く感じられる場合は、以下のことに気をつけましょう。「ほとんどのJavaScriptコードの軽量化では、手の込んだコード変換ではなく、ホワイトスペースの削除とシンボル修飾がサイズ削減の95%を占めています。圧縮を無効化するだけでUglifyのビルドを3~4倍高速化できます」。
Lighthouse CIでTravisの設定にアクセシビリティ、パフォーマンス、SEOスコアを統合することで、プロジェクトに関与している全ての開発者に新たな機能がどのようなパフォーマンス上の影響を与えるかを明らかにすることができます。(画像の出典)(プレビューを拡大)
72. プロキシブラウザと古いブラウザでテストを実施しているか?
ChromeとFirefoxでテストをするだけでは不十分です。プロキシブラウザと古いブラウザでWebサイトがどのように動作するかも調べましょう。例えば、UC BrowserとOpera Miniはアジアで大きな市場シェアを獲得しています(アジアで最大35%)。今後、予想外の大きな問題が起きるのを避けるため、ターゲットとする国の平均的なインターネット速度を測定しましょう。ネットワークスロットリングを使用してテストを実施し、高DPIの端末をエミュレートしましょう。BrowserStackはリモートで実物の端末をテストするための素晴らしいツールです。オフィスで少なくとも数台の実物の端末をテストすることで、BrowserStackのテスト結果を補完することには価値があります。
73. 404ページのパフォーマンスをテストしているか?
通常、404ページについてよく考えることはありません。クライアントがサーバに存在しないページをリクエストしたとき、サーバは404ステータスコードと、関連する404ページを返します。それだけのことではないのでしょうか。
404応答において重要なのは、ブラウザに送信される実際の応答の本文サイズです。Matt Hobbsの404ページの調査によると、404応答の大部分は、失われたファビコン、WordPressのアップロードリクエスト、壊れたJavaScriptリクエスト、マニフェストファイル、CSSとフォントファイルに由来します。クライアントは、存在しないアセットをリクエストするたびに404応答を受け取ります。その応答のサイズはとても大きいことが多いのです。
404ページのキャッシング戦略を確実に検証し、最適化しましょう。目標は、ブラウザがHTML応答を予期しているときにのみHTMLをブラウザに提供し、その他の全ての応答には小規模なエラーのペイロードを返すことです。Mattによれば、「CDNをオリジンの前に配置した場合、CDN上に404ページ応答をキャッシュできる可能性があります。これは便利だと言えます。なぜなら、そうしなければ、404ページへのアクセスが、オリジンサーバを全ての404リクエストに応答させることによるDoS攻撃の経路として利用される可能性があるからです。この攻撃には、オリジンサーバの代わりに、CDNがキャッシュ済みのバージョンによって応答することで対応できます」。
404エラーはパフォーマンスに悪影響を及ぼすだけでなく、トラフィックのコストを大きくします。ですから、Lighthouseのテストに404エラーページを含め、そのスコアを時系列で追跡するのは良いアイデアだと言えます。
74. GDPR同意プロンプトのパフォーマンスをテストしているか?
現在は一般データ保護規則(GDPR)とカリフォルニア州消費者プライバシー法(CCPA)の時代であり、EUの顧客向けにトラッキングのオプトインとオプトアウトの選択肢を提供するためにサードパーティを利用することが一般的となりました。しかし、他のあらゆるサードパーティスクリプトと同様に、GDPRに関するサードパーティのパフォーマンスは、パフォーマンス向上のための取り組み全体にとても破壊的な影響をもたらす可能性があります。
もちろん、実際に顧客が同意するかどうかによって、スクリプトがパフォーマンス全体にもたらす影響は変わるでしょう。そこで、Boris Schapiraが述べているとおり、以下のさまざまなケースのWebパフォーマンスを調べるのが良いかもしれません。
- 同意が完全に拒否された場合
- 同意が部分的に拒否された場合
- 完全な同意が得られた場合
- ユーザが同意プロンプトに反応しなかった場合(またはプロンプトがコンテンツブロッカによってブロックされた場合)
通常、cookie同意プロンプトはCLSに影響を与えないはずですが、場合によっては影響を与えます。ですから、無料のオープンソースオプションであるOsanoやcookie-consent-boxの使用を検討しましょう。
一般的に、ポップアップのパフォーマンスは検討に値します。マウスイベントの縦や横のオフセットを決定したり、ポップアップをアンカーに対して適切に設置したりする必要があるからです。Noam Rosenthalは、Web performance case study: Wikipedia page previewsという記事でWikimediaチームの教訓を共有しています(動画と議事録も提供されています)。
75. パフォーマンス診断CSSを保持しているか?
パフォーマンスが悪いコードが導入されていないかを調べるには、あらゆる種類のチェック方法を採用することができます。簡単に解決できて、すぐに一定の成果を上げられる問題を手早く知れれば、役に立つことが多いはずです。これに関しては、Tim Kadlecの素晴らしいPerformance Diagnostics CSS(Harry Robertsのスニペットにインスパイアされたものです)を使用できます。これは、読み込みが遅い画像、寸法が合っていない画像、古い形式の画像、同期スクリプトを明らかにするものです。
例えば、アバブ・ザ・フォールドのどの画像の読み込みも遅延しないようにしたいとします。この場合、使われていないWebフォントをハイライトする、アイコンのフォントを検知するなど、ニーズに合わせてスニペットをカスタマイズすることが可能です。この小規模なツールは、デバッグの間にミスを可視化したり、単純に現在のプロジェクトをとても高速に監査したりできる素晴らしい存在です。
/* Performance Diagnostics CSS */
/* via Harry Roberts. https://twitter.com/csswizardry/status/1346477682544951296 */
img[loading=lazy] {
outline: 10px solid red;
}
76. アクセシビリティへの影響をテストしているか?
ブラウザはページの読み込みを開始するときにDOMを構築します。スクリーンリーダが実行中であるなど、ユーザ援助技術が存在する場合は、アクセシビリティツリーも作成します。スクリーンリーダは、アクセシビリティツリーに対するクエリを行って情報を取得し、それをユーザに提供します。情報提供はデフォルトで行われたり、要求に応じて行われたりします。これには時間がかかることがあります。
Time to Interactiveの速さは通常、ユーザがリンクやボタンをクリックしたり、タップしたりすることによって、どれだけ速くページとのインタラクションができるかという指標を意味します。スクリーンリーダの場合、意味は少し変わります。この場合、Time to Interactiveの速さは、スクリーンリーダが任意のページのナビゲーションを読み上げ、ユーザがインタラクションのために実際にキーボードを打てるまでにどれだけの時間が経過するかを意味します。
Léonie Watsonは、アクセシビリティのパフォーマンス、特に読み込みの遅さがスクリーンリーダの読み上げの遅延に与える影響について、驚くべき講演を実施しています。スクリーンリーダは、速いペースの読み上げや高速なナビゲーションに慣れているため、視力があるユーザよりもせっかちな可能性さえあります。
JavaScriptによるサイズが大きいページやDOMの操作は、スクリーンリーダの読み上げが遅延する原因となります。この分野はまだまだ未開拓ですが、スクリーンリーダはほとんどのプラットフォームで利用できるため(Jaws、NVDA、Voiceover、Narrator、Orca)、ある程度の注意を払い、テストを行うことが必要でしょう。
77. 継続的モニタリングを設定しているか?
WebPageTestのプライベートインスタンスの設定は、高速で制約がないテストを実現するうえで常にメリットがあります。しかし、Sitespeed、Calibre、[SpeedCurve][https://speedcurve.com/]などの継続的モニタリングツールと自動アラートならば、パフォーマンスの全体像をもっと詳しく知ることができます。独自のUser Timingの測定ポイントを設定し、自社のビジネスに固有の指標を測定・モニタリングしましょう。また、時系列の変化をモニタリングするため、自動化されたパフォーマンス悪化アラートの追加を検討しましょう。
時系列のパフォーマンスの変化をモニタリングするため、RUMソリューションの使用を検討してみてください。ユニットテストと同様の読み込みテストを自動化するツールとして、k6とそのスクリプティングAPIが使用できます。SpeedTracker、Lighthouse、Calibreもチェックしてみましょう。
手軽に成果を上げる #
このリストはとても包括的なので、全ての最適化を完了するにはかなりの時間がかかるかもしれません。もし1時間だけで大きな改善を実現する必要があるとしたら、何をすべきでしょうか。そこで、リスト全体を17の手軽に達成できる項目にまとめてみました。もちろん、最適化の開始前と終了後に結果を測定するようにしてください。測定対象には3G接続と有線接続のLargest Contentful PaintとTime to Interactiveが含まれます。
- 現実世界のユーザ体験を測定し、適切な目標を設定する。最も速い競合サイトよりも20%以上速くなることを目指す。低速な3GでLargest Contentful Paint < 2.5秒、First Input Delay < 100ミリ秒、Time to Interactive < 5秒、リピート訪問の場合はTTI < 2秒を維持する。少なくともFirst Contentful PaintとTime To Interactiveを最適化する。
- Squoosh、mozjpeg、guetzli、pingo、SVGOMGで画像を最適化し、画像CDNでAVIF/WebPを提供する。
- 主要なテンプレート向けのクリティカルCSSを準備し、それを各テンプレートの
<head>
に埋め込む。CSS/JSについては、クリティカルなファイルの容量を、gzip形式の圧縮後で最大170KBのバジェットに収める(解凍すると0.7MB)。 - スクリプトの縮小、最適化、遅延、遅延読み込みを行う。バンドラの設定に注力し、冗長性を削除し、別の軽い選択肢を探す。
- いつでも静的アセットをセルフホストし、サードパーティアセットをなるべくセルフホストする。サードパーティのスクリプトの影響を抑える。ファサードを使用し、インタラクション時にウィジェットを読み込む。ちらつきを防ぐためのスニペットに気をつける。
- フレームワークを選択するときは対象を絞り込む。シングルページアプリケーションについては、クリティカルなページを特定し、それを静的に提供する、あるいは少なくともプリレンダリングする。さらに、コンポーネントのレベルでプログレッシブハイドレーションを使用し、インタラクション時にモジュールをインポートする。
- クライアントサイドのレンダリングのみでは、パフォーマンスにとって良い選択とは言えない。ページがあまり変化しない場合はプリレンダリングを行い、可能ならばフレームワークの起動を遅らせる。可能な場合はストリーミングサーバサイドレンダリングを使用する。
<script type="module">
とmodule/nomoduleパターンによって、古いコードは古いブラウザのみに提供する。- CSSルールの再グループ化を試し、bodyタグ内のCSSをテストする。
- リソースヒントを追加し、比較的高速な
dns-lookup
、preconnect
、prefetch
、preload
、prerender
によってデリバリを高速化する。 - Webフォントをサブセット化し、非同期で読み込む。最初のレンダリングを高速化するため、CSSで
font-display
を使用する。 - HTTPキャッシュヘッダとセキュリティヘッダが適切に設定されていることを確認する。
- サーバ上のBrotli圧縮を有効化する(不可能である場合、少なくともgzip圧縮を確実に有効化する)。
- サーバがLinuxカーネルのバージョン4.9以降で実行されている場合、TCP BBRによる輻輳制御を有効化する。
- 可能な場合、OCSPステープリングとIPv6を有効化する。常にOCSPステープリングされたDV証明書を提供する。
- HTTP/2向けのHPACK圧縮を有効化し、利用できる場合はHTTP/3に移行する。
- フォント、スタイル、JavaScript、画像などのアセットをサービスワーカのキャッシュにキャッシングする。
チェックリストのダウンロード(PDF、Apple Pages)#
このチェックリストを念頭に置けば、どんな種類のフロントエンドのパフォーマンス最適化プロジェクトにも備えることができるでしょう。すぐに印刷できるPDF版や、ニーズに合わせてカスタマイズできる編集可能なApple Pagesドキュメント版のチェックリストをお気軽にダウンロードしてください。
- PDFでチェックリストをダウンロードする(PDF、166KB)
- Apple Pagesでチェックリストをダウンロードする(.pages、275KB)
- MS Wordでチェックリストをダウンロードする(.docx、151KB)
他の選択肢が必要ならば、Dan Rublicのフロントエンドチェックリスト、Jon Yablonskiの"Designer’s Web Performance Checklist"、FrontendChecklistもチェックしてみましょう。
さあ、始めよう!#
一部の最適化は、業務や予算の範囲を超えていたり、処理すべきレガシーコードに対して行き過ぎていたりするかもしれません。それでも大丈夫です。このチェックリストは一般的な(願わくば包括的な)ガイドとして使用し、皆さんの状況に合った独自の課題リストを作成してください。しかし、最も重要なのは、最適化の前にプロジェクトをテスト・測定して課題を特定することです。皆さんの2021年のパフォーマンス最適化が良い結果を生みますように!
この記事をレビューしてくれたGuy Podjarny、Yoav Weiss、Addy Osmani、Artem Denysov、Denys Mishunov、Ilya Pukhalski、Jeremy Wagner、Colin Bendell、Mark Zeman、Patrick Meenan、Leonardo Losoviz、Andy Davies、Rachel Andrew、Anselm Hannemann、Barry Pollard、Patrick Hamann、Gideon Pyzer、Andy Davies、Maria Prosvernina、Tim Kadlec、Rey Bango、Matthias Ott、Peter Bowyer、Phil Walton、Mariana Peralta、Pepijn Senders、Mark Nottingham、Jean Pierre Vincent、Philipp Tellis、Ryan Townsend、Ingrid Bergman、Mohamed Hussain S. H.、Jacob Groß、Tim Swalling、Bob Visser、Kev Adamson、Adir Amsalem、Aleksey Kulikov、Rodney Rehm、ならびに、パフォーマンス最適化の取り組みから学んだテクニックと教訓を誰もが使えるように共有してくれたすてきなコミュニティに心から感謝します。皆さんは本当に最高です。
※編注:本記事は2021年4月時点に公開されていた原文記事を翻訳した内容となります。翻訳記事公開にあたり、2023年6月時点で原文にてリンク切れとなっている箇所の削除や注記、原文に現在表示されていない内容を掲載している場合がございます。ご了承ください。
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa