103 Early Hintsで始める、 サーバー・フロントの協調最適化 1
Webページの表示には通信が必須 サーバーがHTMLを生成している間 描画やリソースの取得は止まる 2
Early Hintsが解決すること HTML生成中に CSS / JS / font などのリソース取得のヒントを送信 結果、画面描画が早くなる! HTMLのレンダリング中に、リソースヒントが並行して始まっている様子 3
HTML生成中の待ち時間をうまく使うのが HTTP 103 Early Hints 4
このセッションのゴール バックエンドで どう実装するのか ブラウザが どう解釈・活用するのか どのUX指標が どれだけ改善するのか 5
プロフィール 発表者 ma@me 所属 最近の業務 品質改善・不具合対応 6
1xx のステータスコードの特徴 1回のリクエストを受けて、次のようなレスポンスを返す 1. Headerのみの、ヒントのレスポンス 2. Bodyを含む、最終レスポンス ブラウザ Request 1xx 暫定レスポンス(ボディなし) Server 処理は継続 最終レスポンス(2xx など) 7
レスポンスが2回返っている HTML リクエストに 103 と 200 が並んでいる様子 1つがヒントの中間レスポンス、1つが最終レスポンス 8
Early Hintsのケース 通常の200ステータスのケース 9
103 応答ヘッダーの中身 約165バイトのヘッダー Link: 〜の部分が先行取得の指示 server: 103の送出箇所。このケースではFrankenPHPのCaddyから送出 10
Early Hintsを含む 歴代の先行取得手法を紹介 11
① HTTP/2 Server Push ブラウザで再現できないので、イメージ図 登場年代:2015 サーバーが要求されていないリソースをHTMLと同時に押し出す仕組み。 ブラウザのキャッシュ状況をサーバーが把握できず、不要なリソースまで送 ってしまうため廃止 12
② <link rel="preload"> <link rel="preload" href="/style.css" as="style"> <link rel="preload" href="/app.js" as="script"> 登場年代:2016 指定したリソースを優先的に先行取得するようブラウザに指示する HTML内で完結するため、サーバー側の対応が不要。主要ブラウザで広く対 応済み HTMLとは直列の取得になる 13
③ 103 Early Hints 登場年代:RFC 2017 / ブラウザ実装 2022 HTML生成中にリソースの先行取得を始めるため、HTMLとリソースの取得 が並列で進む。 14
各手法と取得タイミングまとめ 手法 タイミング 主導権 HTTP/2 Server Push レスポンス時 サーバー <link rel="preload"> HTML到達後 ブラウザ 103 Early Hints HTML生成中 サーバー 15
103は、本レスポンス到達前の空白時間を有効活用 ① <link rel="preload"> ― HTML到達後に発火 ③ 103 Early Hints ― 本レスポンス(200)到達前にヒント送信 16
103 Early Hints に対してサーバー側が どう主導権を握り どう扱うのか サーバー構成での実装を通して紹介 17
PHPでの実現例 × × PHP:バックエンドの処理を担当 Nginx:103 の転送・送出 Node.js:Nginxのサイドカー。103 を生成 18
構成の全体像 Client フロント Nginx Request 内部Nginx + PHP サイドカー Request Request ② 103 Early Hints ③ 200 OK(本文) 19
の仕事 res.writeEarlyHints() で 103 を生成して、Nginxへ転送 公式ドキュメント: https://nodejs.org/api/http.html#responsewriteearlyhintshintscallback 20
103 を生成・転送する2つの部品 Nginx は受け取った103,200を適宜クライアントへ転送。 location / { early_hints on; # ★ upstream proxy_pass http://hints:3000; proxy_http_version 2; proxy_set_header Host $host; } の 103 をクライアントへ転送 21
Early Hints が届く様子 < HTTP/2 103 Early Hints < Link: </css/demo.css>; rel=preload; as=style < HTTP/2 200 OK < x-powered-by: PHP/8.4.21 ブラウザでの見え方 22
色々大変だな、と感じた方 PHPerなら FrankenPHP がおすすめ! headers_send(103); を差し込めばOK! バックエンドがJSなら Cloudflare CDN 23
FrankenPHPって何?という方へ 過去のFrankenPHP関連の登壇スライド どこまで違う?!PHP実行環境パフォーマンス対決 - mod_php vs php-fpm vs Swoole vs FrankenPHP Node.jsに頼らずにFrankenPHPでリアルタイムWeb通信を実現する Laravel OctaneはFrankenPHPをどう高速化しているのか?ソースコードか ら読み解く、高速化の仕組み 24
実装ができても、どの画面でも効くわけで はない ここからは Early Hints が効く画面を見極め る話 25
103 が効く2つの画面構成 1. サーバー処理が長く、描画開始までの待ち時間が長い 2. クリティカルリソースが固定 26
1. サーバー処理が長く 描画開始までの待ち時間が長い画面 サーバー側の処理で、本レスポンスまでに時間がかかる(目 安:200ms〜) DB クエリ 外部 API 呼び出し など 27
イメージ コンテンツ例 EC のカート / 注文確認 ダッシュボード 検索結果 / 一覧画面 認証後のマイページ 28
2. クリティカルリソースが固定の画面 このページなら必ずこれを読む、リソースがバチっと決まって いる画面 29
LP イメージ コンテンツ例 LCP になるヒーロー画像 Web フォント ファーストビューの CSS エントリ JS 30
パフォーマンス検証 1. サーバー処理が長く、描画開始までの待ち時間が長い 2. クリティカルリソースが固定 実際にどれくらい速くなるのか、計測結果を見ていく。 31
FCP / LCP とは? FCP(First Contentful Paint) 最初のテキストや画像が表示された瞬間 LCP(Largest Contentful Paint) 主要コンテンツ(最大要素)が表示された瞬間 = ユーザーが体感する表示完了 32
計測対象のサイト 同じ HTML を返す2つのエンドポイントを用意。 共通条件 HTML を返すまでの遅延:200ms(両エンドポイント共通) = 200 OK までの TTFB:約 200ms 各アセット / API の遅延:各 200ms ※ TTFB は 103 のヒントが届いた時間になり、描画は始まらな いので参考数値扱い。 33
計測対象のサイト 項目 /demo-no-early /demo-early 103 Early Hints の送信 なし preload 対象アセット — headers_send(103) ) demo.css / inter-bold.woff2 / demo.js あり( ダッシュボード 34
計測結果 指標 なし あり 差 13 ms 9 ms −4 ms FCP 300 ms 308 ms +8 ms LCP 300 ms 308 ms +8 ms 全リソース完了 499 ms 228 ms −271 ms(−54%) TTFB ⚠️ サブリソース別(取得完了) リソース なし あり 差 CSS 441 ms 227 ms −214 ms JS 440 ms 228 ms −212 ms font 682 ms 228 ms −454 ms 画像 695 ms 267 ms −428 ms 35
改善事項 全リソース完了が 499ms → 228ms(−54%)に短縮 CSS → font の発見カスケードが解消し、並列ロードに LCP は元々高速(300ms 前後)で横ばい 36
ネットワークタブ 103なし HTML 完了後に CSS, JS が直列で開始。 font はさらに後(発見カスケード) 103あり HTML 生成中に CSS, JS, font, 画像を並行ダウンロード ※ ①(ダッシュボード)のネットワークタブ実測イメージ(数値は別計測) 37
計測対象 ② クリティカルリソースが固定の画面 38
計測結果 指標 なし あり 差 218 ms 10 ms −208 ms FCP 504 ms 308 ms −196 ms LCP 800 ms 384 ms −416 ms(−52%) 全リソース完了 693 ms 269 ms −424 ms TTFB ⚠️ 39
改善事項 FCP が 504ms → 308ms(−39%)に短縮 LCP が 800ms → 384ms(−52%)に短縮 LCP 計測時点でヒーロー画像はダウンロード完了済み 40
ネットワークタブ 103なし 103対応 ※ ②(クリティカルリソース固定)のネットワークタブ実測イメージ(数値は別計測の中央値) 41
サーバー処理が長いほど効果あり HTML をクライアントへ返すまでの時間が長いページほど、 Early Hints の効果は大きい。 SQL クエリ 外部 API 呼び出し テンプレート描画 etc. 42
103で速度を解決! …とはいかないパターンも どんな構成にも効く銀の弾丸ではない。 効果が出るパターン、出ないパターンを確認。 43
フロント構成との相性問題 ✅ 効果あり MPA / SSR ❌ 効果薄 SPA(CSR中心) 44
なぜ SPA では効果が薄いのか サーバーが返すHTMLが薄く、待ち時間がほとんど生まれない ため、Early Hints の効果が薄い。 メインとなるHTMLのレスポンスタイム <!doctype html> <html lang="ja"> <head>...</head> <body> <div id="root"></div> <script type="module" src="/app.js"></script> </body> </html> ソース全体像 45
❌ とりあえずearly hints は 外したヒントは、かえって遅くなる 103は名前の通り、あくまでヒント。 そのページで使わないリソースを指すと逆効果 Link: </app.css>; rel=preload Link: </unused.css>; rel=preload app.cssは全てのページで利用 unused.cssはindexページでしか利用しないのに、全部のページ で読み込んでいる、など 46
適切に導入すれば ユーザー体験アップ ⤴️ ご清聴ありがとうございました 47