操作して学ぶ
URLを入力してから、画面が表示されるまで
エンジニア面接の定番「ブラウザにURLを打つと何が起きますか?」。 その答えを、HSTS確認からLCPまでの11ステップとして、 目安の経過時間つきでステップ実行できるようにしました。 各ステップの「深掘り」から、裏側の仕組みを解説する記事へ進めます。
URLの解釈とHSTS確認
アドレスバーの入力がURLか検索語かを判定し、スキームを補完します。HSTSプリロードリストに載るドメインなら、最初の1回からHTTPSを強制します。
- 入力文字列をURLとして解釈できなければ既定の検索エンジンへ
- スキーム省略時は https を補完(HSTS登録済みならHTTPアクセス自体が発生しない)
- 国際化ドメインは Punycode へ正規化される
左右の矢印キーでも操作できます。時間は東京→東京・初回アクセスの目安。
全ステップの詳解
上のステップ実行と同じ内容を、通して読める形で並べています。
01 URLの解釈とHSTS確認1ms未満
アドレスバーの入力がURLか検索語かを判定し、スキームを補完します。HSTSプリロードリストに載るドメインなら、最初の1回からHTTPSを強制します。
- 入力文字列をURLとして解釈できなければ既定の検索エンジンへ
- スキーム省略時は https を補完(HSTS登録済みならHTTPアクセス自体が発生しない)
- 国際化ドメインは Punycode へ正規化される
02 キャッシュとService Workerの確認0〜1ms
ネットワークへ出る前に、メモリキャッシュ→ディスクキャッシュ→Service Worker の順で手元の資産を確認します。新鮮なキャッシュがあれば、以降のステップは丸ごとスキップされます。
- fresh なキャッシュ命中なら 0 RTT で描画へ直行(最速のリクエストは「しないリクエスト」)
- 期限切れでも ETag/Last-Modified があれば条件付きリクエストで 304 を狙う
- Service Worker の fetch ハンドラが登録済みならオフライン応答も可能
03 DNS解決 — 名前をIPアドレスへキャッシュ命中 約1ms/フル解決 20〜120ms
ドメイン名をIPアドレスへ変換します。OSやフルリゾルバのキャッシュに無ければ、ルート→TLD→権威サーバーと反復問い合わせが走ります。
- スタブリゾルバ→OSキャッシュ→フルリゾルバ(ISP/パブリックDNS)→ルート→TLD→権威の順
- 各段がTTLに従ってキャッシュするため、人気ドメインはほぼ即答
- DoH/DoT を使えば問い合わせ自体が暗号化される
DNSの基本 ・ DNS解決の内部動作 ・ DoH/DoT
04 TCP 3ウェイハンドシェイク1 RTT ≈ 10ms
SYN → SYN-ACK → ACK の往復で、信頼できるバイトストリームの土台を作ります。ここで初めてサーバーと「会話の回線」がつながります。
- シーケンス番号の初期化とMSS(最大セグメントサイズ)の交渉
- RTT(往復遅延)がそのままコストになる。東京→東京なら数ms、東京→US西海岸なら約100ms
- HTTP/3(QUIC)はUDP上でこの往復をTLSと統合し、接続確立を1 RTTに圧縮する
05 TLS 1.3ハンドシェイク1 RTT ≈ 10ms(再開時は0-RTTも)
ClientHello に鍵共有材料を同梱し、1往復で暗号化通信を確立します。サーバー証明書のチェーンを検証し、相手が本物であることを確かめます。
- TLS 1.3 は key_share の同梱で 1 RTT 確立(TLS 1.2 は 2 RTT だった)
- 証明書チェーン(サーバー証明書→中間CA→ルートCA)を検証
- セッション再開なら 0-RTT でアプリデータを初手から送ることも可能(リプレイ注意)
06 HTTPリクエスト送信片道 ≈ 5ms
メソッド・パス・ヘッダー(Cookie、User-Agent、Accept系)をようやく送出します。ここまでの約40msは、この1通を安全に届けるための準備でした。
- HTTP/2 なら1本の接続上に複数リクエストを多重化できる
- ヘッダーは HPACK(HTTP/2)/ QPACK(HTTP/3)で圧縮される
- Cookieが肥大していると毎リクエストに乗って帯域を食う
07 サーバー処理10〜100ms(アプリ次第で桁が動く)
CDNエッジやロードバランサを経てアプリケーションへ。DBクエリを実行し、HTMLやJSONを組み立てます。旅全体で最も変動が大きい区間です。
- 典型経路: CDNエッジ → LB → リバースプロキシ → アプリ → DB → 応答組み立て
- 遅いSQL・N+1問題・外部API待ちなど、体感速度の主戦場はここ
- CDNで静的資産やページ自体をエッジ配信できれば、この区間を丸ごと短絡できる
08 レスポンス転送とスロースタート50KBのHTMLで20ms前後
HTMLが流れてきます。ただしTCPの輻輳ウィンドウは小さく始まるため、初回は数RTTかけて徐々に加速します。大きな初期HTMLはここで損をします。
- スロースタート: 輻輳ウィンドウがRTTごとに倍々で成長(初期はおよそ10セグメント≈14KB)
- 14KBに収まる「クリティカルなHTML」は1往復で届く——初期表示最適化の古典的な根拠
- gzip/brotli 圧縮で転送バイトを数分の1に削減
09 HTMLパースとサブリソース発見数十ms〜
届いたそばからHTMLを逐次パースしてDOMを組み立てます。同時にプリロードスキャナが先読みし、CSS・JS・画像の取得を並列で開始します。
- パースは逐次的——全部届くのを待たずにDOM構築が始まる
- プリロードスキャナが本パーサより先に走り、リソース取得を前倒しする
- 同期スクリプトはパーサをブロックする。defer/async/type=module で回避
10 スタイル計算 → レイアウト → 描画数十ms
DOMとCSSOMを合成してレンダーツリーを作り、レイアウト(配置計算)→ペイント(ラスタライズ)→コンポジット(合成)と流れて、ついに画素になります。
- レイアウトは要素の幾何を計算、ペイントは描画命令化、コンポジタがGPUで合成
- JSがスタイルを読み書きするとレイアウトの再計算(reflow)が走り得る
- transform/opacity だけのアニメはコンポジタ単独で動くので滑らか
11 最初の描画からLCPへ合計 0.3〜1秒
FCP(最初の描画)を経て、主要コンテンツが表示されるLCPへ。ここまでが「1秒の旅」です。フォント・画像・JSの読み込み戦略がこの数百msを左右します。
- FCP=何かが最初に描画された瞬間、LCP=最大要素が表示された瞬間
- Webフォントの読込戦略(font-display)や画像の優先度がLCPを動かす
- 2回目以降の遷移は bfcache や prerender でほぼ0秒にできる