非新手チュートリアル、ここでは主にパフォーマンス最適化の重要なポイントを列挙します。
ユーザーにとって、ファーストスクリーンのページ読み込みは主にログインページや各役割のホームページなどで表れます。迅速に読み込まれるウェブサイトを構築する第一歩は、サーバーからページの HTML の応答をタイムリーに受け取ることです。ブラウザのアドレスバーに URL を入力すると、ブラウザはそれを取得するためにサーバーに GET リクエストを送信します。HTML が迅速に到達し、遅延が最小限であることを確保することが重要なパフォーマンス目標です。
TTFB#
HTML の初期リクエストは複数のステップを経ており、それぞれのステップに時間がかかります。各ステップにかかる時間を短縮することで、最初のバイト時間(TTFB)を短縮できます。
TTFB はフロントエンドがページの読み込み速度に注目する際に考慮すべき唯一の指標ではありませんが、TTFB が高いと、最大コンテンツ描画(LCP)や最初のコンテンツ描画(FCP)などの指定された「良好」な閾値に達することが難しくなります。ここから得られる簡単な結論は:単にフロントエンドの最適化だけではパフォーマンスを最大限に引き出すことはできず、バックエンドとの連携が必要です。
ほとんどのウェブサイトは、TTFB を 0.8 秒以下に抑えるよう努力すべきです。
Server-Timing
レスポンスヘッダーは、高遅延を引き起こす可能性のあるインターフェースを測定するために使用できます。
// 説明と値を持つ2つのメトリック
Server-Timing: db;desc="データベース";dur=121.3, ssr;desc="サーバーサイドレンダリング";dur=212.2
Server-Timing
レスポンスヘッダーを持つページは、Navigation Timing APIの serverTiming
プロパティを通じて取得できます:
// 最初のナビゲーションリクエストのserverTimingエントリを取得:
performance.getEntries("navigation")[0].serverTiming.forEach(entry => {
// サーバータイミングデータをログに記録:
console.log(entry.name, entry.description, entry.duration);
});
TTFB を最適化する方法については、この記事を参考にしてください:
Optimize Time to First Byte | Articles | web.dev
静的リソース応答圧縮#
静的ファイル(HTML、JavaScript、CSS、SVG 画像など)の応答は圧縮される必要があり、ネットワーク上の伝送コストを削減して、より迅速にダウンロードできるようにします。現在最も広く使用されている圧縮アルゴリズムはgzip
とBrotli
であり、Brotli
はgzip
に比べて約 15% から 20% の改善を提供します。
ほとんどの CDN サービスプロバイダーは通常、自動的に圧縮を設定しますが、圧縮設定を自分で構成または調整できる場合は、できるだけBrotli
を使用してください。Brotli
はgzip
に比べてかなり明確な改善を提供し、すべての主要なブラウザがBrotli
をサポートしています。
できるだけBrotli
を使用してください。ただし、ウェブサイト自体が古いブラウザで多くのユーザーに使用されている場合は、互換性のためにgzip
を使用することを確認してください。なぜなら、圧縮は圧縮しないよりも常に良いからです。
CDN#
コンテンツ配信ネットワーク(CDN)は、分散型サーバーネットワークを利用してユーザーにリソースを配信することで、ウェブサイトのパフォーマンスを向上させます。CDN はサーバーの負担を軽減できるため、サーバーのコストを削減し、トラフィックの急増に対処するのに非常に適しています。
CDN は遅延を減少させることを目的としており、ユーザーに地理的に近いサーバーにリソースを配信することでこれを実現します。そのため、CDN の主な利点は、特に CDN を導入した後、リソースの最初のバイト応答時間(TTFB)が大幅に改善され、これは LCP 指標の向上にとって重要な役割を果たします。
CDN を使用してウェブサイトの読み込み速度を向上させる方法については、この記事を参照してください:
Content delivery networks (CDNs) | Articles | web.dev
blocking="render"
- 実験的機能#
実験的機能として、blocking=render
を属性およびその値として<script>
、<style>
、またはスタイルシートの<link>
タグに追加することで、明示的にレンダリングブロックを設定できます。主な用途は、スタイルが適用されていないコンテンツのフラッシュを防止したり、完全に読み込まれていないページとのユーザーのインタラクションを防ぐことです。この状況は、スクリプトの挿入やスタイルシート、クライアントの A/B テストなどによって引き起こされることが一般的です。
ブラウザの互換性:
"blocking" | Can I use... Support tables for HTML5, CSS3, etc
現在、すべてのブラウザにはレンダリングブロックメカニズムが組み込まれています:ページナビゲーション後、ブラウザは<head>
内のすべてのスタイルシートおよび同期スクリプトが読み込まれ、処理されるまで、画面にピクセルをレンダリングしません。これにより、スタイルが適用されていないコンテンツのフラッシュ現象(FOUC)を防ぎ、フレームコードなどの重要なスクリプトが実行されることを保証し、最初のレンダリングサイクル後にページ機能が正常に使用できるようになります。
https://github.com/whatwg/html/pull/7474
CSS#
最も基本的なレベルで、CSS の圧縮はウェブサイトのパフォーマンスを効果的に向上させる最適化手法であり、ウェブサイトの最初のコンテンツ描画(First Contentful Paint, FCP)を改善し、場合によっては最大コンテンツ描画(Largest Contentful Paint, LCP)を改善することができます。バンドルツール(Webpack、Vite など)は、プロダクション環境のビルドでこれらの最適化を自動的に実行できます。
ブラウザはページコンテンツをレンダリングする前に、すべての CSS スタイルシートをダウンロードして解析する必要があります。この解析プロセスには、現在のページで使用されていないスタイルも含まれますが、これらのスタイルは実際には不要です。使用しているバンドルツールがすべての CSS を 1 つのファイルに結合する場合、ユーザーがダウンロードする CSS が現在のページをレンダリングするために必要な量を超える可能性があります。
Chrome DevTools のカバレッジツールを使用して、現在のページで未使用の CSS(または JavaScript)を検出できます。
JavaScript#
JavaScript はウェブページの大部分のインタラクション機能を担当していますが、その背後にはコストが伴います。
過剰な JavaScript コードの読み込みは、ページの読み込み中に応答が遅くなる原因となり、インタラクションの応答が遅くなる問題を引き起こす可能性があります。
async
およびdefer
属性を使用すると、外部スクリプトを HTML パーサーをブロックせずに読み込むことができ、module
タイプのスクリプト(インラインスクリプトを含む)は自動的に遅延読み込みされます。ただし、async
とdefer
の間のいくつかの重要な違いを理解することが非常に重要です。
出典: https://html.spec.whatwg.org/multipage/scripting.html
async
属性で読み込まれたスクリプトは、ダウンロードが完了するとすぐに解析および実行されますが、defer
属性で読み込まれたスクリプトは HTML ドキュメントの解析が完了するまで実行されません —— これはブラウザの DOMContentLoaded イベントと同期して発生します。同時に、async
属性のスクリプトは順番に実行されない可能性がありますが、defer
属性のスクリプトはページ内に現れる順序に従って実行されます。
さらに、JavaScript の圧縮は他のリソースの圧縮(例えば CSS)よりも徹底的に行われ、JavaScript の圧縮プロセスでは、空白、タブ、コメントなどの非コードコンテンツだけでなく、ソースコード内の変数や関数名も短縮されます。このプロセスは時折「難読化」(uglification)とも呼ばれます。
Preconnect
予接続#
preconnect
を使用することで、ブラウザが特定のクロスオリジンサーバーに接続する必要があることを予測し、ブラウザはその接続を即座に開始するべきです。理想的には、HTML パーサーやプリロードスキャナーが作業を開始する前に行われるべきです。
preconnect
は Google フォントサービスでよく使用されます。Google フォントは、@font-face 宣言を提供するためにhttps://fonts.googleapis.comドメインへの事前接続を推奨し、フォントファイルを提供するためにhttps://fonts.gstatic.comドメインへの接続も推奨しています。
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
dns-prefetch
DNS プリフェッチ#
クロスオリジンサーバーへの接続を事前に開くことでページの初期読み込み時間を大幅に短縮できますが、ウェブページが同時に複数のクロスオリジン接続を確立することは合理的ではない場合があります。
preconnect
の過剰使用が懸念される場合、よりリソースを節約する方法はdns-prefetch
を使用することです。名前が示すように、dns-prefetch
はサーバー接続を確立するのではなく、ドメイン名の DNS 解決のみを行います。
ドメイン名を対応する IP アドレスに解決する過程では、デバイスおよびネットワークレベルの DNS キャッシュがこのプロセスを加速できますが、いくつかの時間を消費します。
preload
プリロード#
preload
指令は、ページレンダリングに必要なリソースを事前にリクエストするために使用されます:
<link rel="preload" href="/font.woff2" as="font" crossorigin>
<link>
要素がpreload
指令でas
属性を設定していない場合、リソースは 2 回ダウンロードされます。さまざまなas
属性の値については、MDN ドキュメントの関連説明を参照してください。
prefetch
プリフェッチ#
prefetch
指令は、将来のナビゲーションで使用される可能性のあるリソースの低優先度リクエストを開始するために使用されます:
<link rel="prefetch" href="/next-page.css" as="style">
特定のユーザーフローを特定できる場合、prefetch
は非常に役立ちます。これにより、将来のページの重要なリソースを事前に読み込むことで、それらの読み込み時間を短縮できます。
注意:prefetch
は本質的に推測的な操作であるため、潜在的な欠点は、ユーザーが最終的にそのプリロードリソースを必要とするページにアクセスしなかった場合、リソースを取得するために消費されたデータが無駄になる可能性があることです。ウェブサイトの分析データや他の使用パターン情報に基づいて、prefetch
を適用するかどうかを判断する必要があります。また、データ使用を減らすことを好むユーザーに対しては、Save-Data
ヒントを利用してプリロード操作を避けることもできます。
一般的に、<link rel="prefetch">
を使用してクロスオリジン文書をプリフェッチすることは避けることをお勧めします。これは、重複リクエストを引き起こす可能性があるクロスオリジン文書のプリフェッチに関する公開問題があります。 同様に、認証セッションのために動的に生成された HTML 応答など、個別化された同一オリジン文書のプリフェッチも避けるべきです。これらのリソースは一般的にキャッシュされず、使用されずに放置される可能性が高く、最終的に帯域幅を無駄にします。
Chromium ベースのブラウザでは、Speculation Rules APIを使用して文書をプリフェッチできます。Speculation Rules は JSON オブジェクトとして定義され、ページの HTML に埋め込むか、JavaScript を介して動的に注入できます:
<script type="speculationrules">
{
"prefetch": [{
"source": "list",
"urls": ["/page-a", "/page-b"]
}]
}
</script>
Quicklinkのようなライブラリは、ユーザーの視界内のページリンクを動的にプリフェッチまたはプリレンダリングすることでページナビゲーション体験を最適化します。これは、ページ上のすべてのリンクをプリフェッチする方法と比較して、ユーザーが最終的にこれらのページを閲覧する可能性を高める方法です。
Prerender
プリレンダリングページ#
リソースをプリフェッチするだけでなく、ブラウザを使用してユーザーが今後アクセスするページを事前にレンダリングすることもできます。この方法により、ページとそのリソースがバックグラウンドで事前に読み込まれ、処理されるため、ほぼ瞬時にページが読み込まれます。ユーザーがそのページにアクセスすると、すぐに表示されます。
プリレンダリング機能は、Speculation Rules API を使用して実現できます:
<script type="speculationrules">
{
"prerender": [
{
"source": "list",
"urls": ["/page-a", "page-b"]
}
]
}
</script>
Chrome は、<link rel="prerender" href="/page">
のようなリソースヒントの方法もサポートしています。ただし、Chrome 63 以降、この方法は無状態プリフェッチ(NoState Prefetch)を導入し、ページに必要なリソースを読み込むためだけに使用され、ページのレンダリングや JavaScript の実行は行いません。
完全なプリレンダリングは、プリレンダリングページ内の JavaScript も実行します。JavaScript は大きなサイズで計算量が多いリソースであるため、prerender
を使用する際はできるだけ慎重に行い、ユーザーがそのプリレンダリングページにアクセスする準備ができていると確信している場合にのみ使用することをお勧めします。
Service Worker プリキャッシュ#
Service Worker のプリキャッシュ機能は、Cache APIを使用してリソースを取得し、保存することができ、これによりブラウザはネットワーク操作なしで Cache API を介してリクエストに応答できます。Service Worker のプリキャッシュは、いわゆる「キャッシュのみの戦略」という非常に効率的なキャッシュ戦略を採用しており、一度 Service Worker キャッシュに保存されたリソースは、リクエスト時にほぼ瞬時に取得できます。
Service Worker を使用してリソースをプリキャッシュするには、Workboxというツールを使用できます。もちろん、手動で制御したい場合は、特定のファイルセットをキャッシュするためのコードを自分で記述することもできます。
リソースのプリキャッシュを実現する方法に関係なく、このプロセスはService Worker のインストール時に行われます。一度インストールが完了すると、すべてのプリキャッシュされたリソースは、あなたのウェブサイト上の Service Worker が管理する任意のページから呼び出して使用できます。
Workbox | Chrome for Developers
リソースヒントや推測ルールを使用してリソースをプリフェッチまたはプリレンダリングするのと同様に、Service Worker のプリキャッシュもネットワーク帯域幅、ストレージスペース、CPU 処理能力を消費します。したがって、使用される可能性のあるリソースのみをプリキャッシュすることをお勧めし、プリキャッシュリストに過剰なリソースを含めないようにしてください。どのリソースをプリキャッシュする必要があるか不明な場合は、少しだけキャッシュし、Service Worker キャッシュの充填作業をランタイムキャッシュに任せ、読み込み速度とリソースの更新度を調整するために複数のモードを採用する方が良いです。プリキャッシュリソースに関するさらなる実践的なヒントや禁忌については、プリキャッシュの正しい方法と間違った方法をお読みください。
Fetch Priority API
#
fetchpriority
属性を使用することで、リソースの読み込み優先度を向上させることができます。この属性は<link>
、<img>
、および<script>
要素に適用されます。
<div class="gallery">
<div class="poster">
<img src="img/poster-1.jpg" fetchpriority="high">
</div>
<div class="thumbnails">
<img src="img/thumbnail-2.jpg" fetchpriority="low">
</div>
</div>
画像#
画像は通常、ウェブページで最も大きく、最も一般的なリソースです。したがって、画像を最適化することでウェブページのパフォーマンスを大幅に向上させることができます。ほとんどの場合、画像を最適化することは、伝送データ量を減らしてネットワーク伝送時間を短縮することを意味しますが、ユーザーのデバイスサイズに適した画像を提供することで伝送データ量を最適化することもできます。
現代のブラウザはさまざまな画像ファイル形式をサポートしています。PNG や JPEG に比べて、現代の画像形式であるWebPやAVIFは、より良い圧縮効果を提供し、画像ファイルのサイズを小さくし、ダウンロード時間を短縮します。現代の形式で画像を提供することで、リソースの読み込み時間を短縮し、最大コンテンツ描画(Largest Contentful Paint, LCP)を低下させる可能性があります。
JavaScript コード分割#
大規模な JavaScript リソースを読み込むことは、ページの読み込み速度に深刻な影響を与えます。JavaScript をより小さなチャンクに分割し、ページの起動時にページ機能に必要なコードのみをダウンロードすることで、ページの読み込み応答速度を大幅に向上させることができます。
ページが大規模な JavaScript ファイルをダウンロード、解析、コンパイルしているとき、一時的な無応答状態が発生する可能性があります。ページ要素はすでに表示されているため、これらはページの最初の HTML に属し、CSS スタイルが適用されています。しかし、これらのインタラクティブ要素を駆動する JavaScript やページに読み込まれた他のスクリプトが実行されている可能性があり、これにより機能が正常に動作しなくなり、ユーザーはインタラクションが明らかに遅延しているか、完全に利用できないと感じる可能性があります。
Lighthouse は、JavaScript の実行時間が 2 秒を超えると警告を表示し、3.5 秒を超えると失敗と見なします。ページライフサイクルのどの段階においても、過剰な JavaScript の解析と実行は問題を引き起こす可能性があります。なぜなら、これはユーザーがページとインタラクションする際に入力遅延を増加させる可能性があるからです。特に、JavaScript の処理と実行タスクが主スレッドで同期して実行される場合です。
さらに、過剰な JavaScript の実行と解析は、ページの初期読み込み中に特に問題となります。なぜなら、この時点でユーザーはページとインタラクションする可能性が非常に高いからです。実際、総ブロッキング時間(Total Blocking Time, TBT)— 読み込み応答性を測定する指標 — は、インタラクションから次の描画(INP)と高度に関連しています。これは、ユーザーがページが最初に読み込まれたときにインタラクションを試みる可能性が高いことを意味します。
動的import()
関数を使用することで、コード分割を実現できます。この関数は、特定の JavaScript リソースを起動時にリクエストする<script>
要素とは異なり、ページライフサイクルの後期段階で JavaScript リソースをリクエストできます。
document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
// モジュールからのフォーム検証の名前付きエクスポートをデストラクチャリングを通じて取得:
const { validateForm } = await import('/validate-form.mjs');
// フォームを検証:
validateForm();
}, { once: true });