はじめに
2月14日に、リ○○ート(一応伏せた方がいいらしいので)が主催するフロントエンドパフォーマンスチューニングのハッカソンに参加する予定でした。実際のサイトを題材に、どれだけパフォーマンスを改善できるかを競うという内容です。
ですが、体調を崩してしまい参加できませんでした。
せっかく準備した過程で色々と学べたので、記録として残しておきます。
自分のフロントエンド知識
普段はバックエンドやアルゴリズム寄りのことをやっていて、フロントエンドにはほとんど馴染みがありませんでした。HTMLやCSSは「なんとなく書ける」程度で、ブラウザの内部で何が起きているかについてはほぼ考えたことがなかったです。
ハッカソンに参加しようと思ったのは、単純に面白そうだったのと、知らない領域に触れてみたかったからです。大規模なWebサービスのパフォーマンス改善は実践的な課題ですし、良い機会だと思いました。
ただ、その時点では「Core Web Vitalsって何?」「Lighthouseってどう使うの?」というレベルだったので、2週間弱でなんとかキャッチアップする必要がありました。
頼った本
学習の中心に使ったのは、『Webフロントエンドパフォーマンスチューニング』という本です。
この本を選んだのは、チューニングのテクニック集ではなく、ブラウザがどのようにページをレンダリングするかという仕組みの部分から解説してくれている点が良かったからです。テクニックを暗記しても応用が利かないので、原理を理解しておいた方がハッカソンのような未知の課題に対応しやすいだろうと考えました。他にも良さそうな本はいくつかありましたが、時間に余裕もなかったのでこの1冊に絞り勉強することにしました。
ブラウザレンダリングの流れ
特に何度も読み返したのが、「ブラウザがレンダリングするまでの流れ」のセクションです。
ブラウザがWebページを表示するまでには、大まかに以下のステップがあります。
- HTMLのパース → DOMツリーの構築
- CSSのパース → CSSOMツリーの構築
- DOMとCSSOMの合成 → レンダーツリーの構築
- レイアウト(リフロー) → 各要素の位置とサイズを計算
- ペイント → ピクセルを描画
- コンポジット → レイヤーを合成して画面に表示
この流れを押さえておくと、各チューニング手法がなぜ効くのか理解しやすくなります。
例えば、「CSSはhead内に置くべき」というプラクティスは、CSSOMの構築がレンダーツリーの生成をブロックするので、CSSは早く読み込ませた方がいいということです。逆に、JavaScriptの<script>タグにはbodyの末尾に置くかdeferやasync属性をつけるのが良いとされますが、これはJSの実行がDOMのパースをブロックしてしまうからです。
こういった定番の手法が、レンダリングの流れを理解した上だと自然と腑に落ちます。自分はこのセクションを3回以上読み返して、新しいテクニックに出会うたびに「これはどのステップに関係しているんだろう」とこの流れに立ち戻るようにしていました。
レンダリングブロックとクリティカルレンダリングパス
もう一つ重要だと感じたのが、クリティカルレンダリングパス(Critical Rendering Path)の概念です。
ユーザーがページにアクセスしてから最初のコンテンツが表示されるまでに、ブラウザが通る最短経路のことです。この経路上にあるリソースが「レンダリングブロックリソース」で、これが多いほど初期表示が遅くなります。
パフォーマンスチューニングの大きな方針の一つは、このクリティカルレンダリングパスをいかに短くするかです。不要なCSSやJSをファーストビューから排除して、最小限のリソースだけでレンダリングを開始できるようにします。
本を読んで印象に残ったのは、ブラウザはもらったリソースを律儀に全部処理しようとするということです。開発者が「これは今すぐ必要」「これは後でいい」と明示的に伝えないと、ブラウザは全部揃うまでレンダリングを待ってしまいます。preload、prefetch、defer、async、loading="lazy"などの属性は、ブラウザにリソースの優先度を教えるための仕組みです。
Core Web Vitalsについて
パフォーマンス改善をするなら、まず何を指標にするかを知る必要があります。Googleが定義しているCore Web Vitalsについて整理しておきます。
LCP(Largest Contentful Paint)
ページ内で最も大きなコンテンツ要素が表示されるまでの時間です。ユーザーが「ページが読み込まれた」と感じるタイミングに近く、2.5秒以内が良好とされています。
改善するには、最大要素(ヒーロー画像やメインのテキストブロックなど)を素早く表示させること、不要なリソースに邪魔させないことが大切です。
INP(Interaction to Next Paint)
ユーザーの操作(クリック、タップ、キー入力)から、その結果が画面に反映されるまでの時間です。200ミリ秒以内が良好とされています。
JavaScriptのメインスレッド占有が大きく関わっていて、重いJSの処理がメインスレッドを独占していると、ユーザー操作に対するレスポンスが遅れます。
CLS(Cumulative Layout Shift)
ページの読み込み中にレイアウトがどれだけズレるかを数値化したものです。0.1以下が良好。
画像にwidthとheightを指定していない、フォントの読み込みでテキストが変わる、動的コンテンツの挿入などが原因になります。地味ですが、ユーザー体験にかなり影響する指標です。
実際に手を動かす
本で理論を学んだ後は、実際にツールを使いながら感覚を掴む練習をしました。
Lighthouseで計測する
まずLighthouseでページのパフォーマンスを計測する練習をしました。Chrome DevToolsのLighthouseタブから実行できます。
実行すると、Performance、Accessibility、Best Practices、SEOの各スコアが表示されます。パフォーマンスのセクションではLCP、INP、CLSなどのメトリクスに加えて、具体的な改善提案も出てきます。
初めて動かしたときは、思った以上に具体的な指摘をしてくれることに驚きました。「この画像は次世代フォーマットを使いましょう」「使われていないCSSが○KBあります」「レンダリングブロックリソースがあります」といった内容で、改善の方向性がそのまま見えてきます。
ただ、Lighthouseのスコアは実行ごとに多少変動しますし、環境にも依存します。一回の結果を鵜呑みにせず、複数回実行して傾向を見るのが大切です。
Chrome DevToolsで深掘りする
Lighthouseで全体像を掴んだ後は、Chrome DevToolsのNetworkタブとPerformanceタブで詳しく見ていきました。
Networkタブでは、各リソースの読み込み時間・サイズ・順序をウォーターフォールチャートで確認できます。どのリソースがボトルネックになっているか分かりやすいです。
Performanceタブでは、ブラウザが実際にやっていることをタイムライン上で確認できます。JSの実行、レイアウト計算、ペイントなどがフレーム単位で見えるので、「このJSの実行に200msかかっている」「ここでリフローが連鎖している」といったことが把握できます。
最初はPerformanceタブの出力は情報量が多くて何を見ればいいか分からなかったのですが、レンダリングの流れを理解してからだと「ここでDOMの構築が止まっているのはこのスクリプトのせいだな」と読めるようになりました。本で学んだ理論が、ツールの見方にそのまま繋がっていると感じた場面です。
コードを修正して効果を確認する
計測だけでなく、実際にコードを修正して変化を確認するところまで練習しました。やったことの例です。
- 画像の最適化:
<img>タグにwidthとheightを明示してCLSを改善。loading="lazy"でファーストビュー外の画像の読み込みを遅延。WebPなどの次世代フォーマットへの変換。 - CSSの最適化: 使われていないCSSの削除。クリティカルCSSのインライン化(ファーストビューに必要なスタイルだけ
<style>タグで直接埋め込む)。 - JavaScriptの最適化:
defer属性でパース時のブロックを回避。大きなバンドルの分割(コードスプリッティング)。 - リソースヒントの活用:
<link rel="preload">で重要なリソースの先読み。<link rel="preconnect">でサードパーティドメインへの接続を事前確立。
どれも本で読んだ知識の実践ですが、実際に手を動かしてスコアの変化を見ると、理解が一段深まります。
振り返り
原理から理解するアプローチ
今回の準備で一番良かったのは、最初にブラウザのレンダリングの仕組みを理解したことです。
パフォーマンスチューニングのテクニックは数が多いですし、ネット上にも「これをやればスコアが上がる」という記事はたくさんあります。ただ、なぜそれが効くのかを理解していないと、初めて見るページに対してどのテクニックを使うべきか判断しにくいです。
ハッカソンのような場では、事前に知っている定番サイトではなく、初見のページに対してチューニングする必要があります。そのとき頼りになるのは暗記したテクニックではなく、ボトルネックがどこにあるかを分析する力で、その分析力はブラウザの内部動作を理解していないと身につきません。
フロントエンドの奥深さ
正直なところ、以前はフロントエンドに対して「見た目をいじる仕事」という印象がありました。ですが今回の学習を通じて、ブラウザエンジンの仕組みやネットワーク最適化、レンダリングパイプラインといった深い技術的な層があることを知りました。
レンダリングパイプラインはコンパイラのパイプラインと似た部分もあって、CS的な知識が活きる場面が多いです。バックエンド中心にやってきた自分にとって、フロントエンドは思っていたよりずっと奥が深い領域でした。
体調管理
最後に、体調を崩して本番に出られなかったという点。どれだけ準備しても、当日のコンディションが整っていなければ意味がないです。特にハッカソンのような短期集中のイベントでは、体力と集中力が直接パフォーマンスに響きます。Webのパフォーマンスチューニングも大事ですが、自分自身のコンディション管理も同じくらい大事だなと改めて思いました。
おわりに
ハッカソンには参加できませんでしたが、準備の過程で得たものは多かったです。ブラウザの内部動作への理解、LighthouseやDevToolsを使った計測と改善のサイクル、Core Web Vitalsの考え方。これらは今後の開発でも活かせると思います。
次に同じようなイベントがあれば、今度はちゃんと体調を整えて参加したいです。
参考書籍