コンパイラによるウェブパフォーマンス:Rich Harris の視点
ランタイム重視のフレームワークとコンパイル時出力を比較し、コンパイラ優先アプローチがどのようにウェブパフォーマンスを改善するかと、実用的な意思決定フレームワークを解説します。

なぜ良い端末でも一部のウェブアプリは遅く感じるのか
ユーザーは滅多に技術的な言葉でパフォーマンスを語りません。アプリが“重い”と感じると言います。ページが何かを表示するまでにワンテンポ遅れる、ボタンの反応が遅い、検索入力やタブ切り替えのような単純な操作がカクつく──といったことです。
症状は馴染み深いものです:初回読み込みが遅い(空白や中途半端な UI)、操作がもたつく(クリックに遅延、スクロールのジャイター)、保存やフィルタのように瞬時に感じられるべき操作で長いスピナーが出る。
多くはランタイムコストです。簡単に言えば、ページが読み込まれた後にブラウザがアプリを使える状態にするためにやらなければならない作業です:追加の JavaScript をダウンロードして解析し実行し、UI を組み立て、ハンドラを取り付け、さらに更新ごとに追加作業をし続けます。速い端末でも、ブラウザに通せる JavaScript には限界があり、その限界を超えると体験が落ちます。
パフォーマンス問題は遅れて現れることもあります。初期はアプリは小さい:数画面、軽いデータ、シンプルな UI。そこからプロダクトは成長します。マーケティングがトラッカーを追加し、デザインがリッチなコンポーネントを入れ、チームは状態管理や機能、依存関係、パーソナライズを積み重ねます。各変更は単体では無害に見えても、合計すると負担になります。
だからチームはコンパイラ優先のパフォーマンス考え方に注目し始めます。目標は必ずしも完璧なスコアではありません。毎月アプリが遅くならないようにしつつ、継続的に出荷することです。
ランタイム重視とコンパイル時の基本的な違い
ほとんどのフロントエンドフレームワークは二つのことを助けます:アプリを構築すること、そして UI をデータに同期させ続けること。重要なのは後者がいつ行われるかです。
ランタイム重視のフレームワークでは、ページ読み込み後にブラウザ側でより多くの作業が起こります。汎用のランタイムを配布して、変更を追跡し、何を更新すべきか判断し、更新を適用する。柔軟性は開発時に有利ですが、その分ユーザーに渡す JavaScript が増え、UI が準備できるまでのダウンロード・解析・実行コストが増えることが多いです。
コンパイル時最適化では、その作業の多くをビルド工程に移します。ブラウザに一連の一般的なルールを送る代わりに、ビルドツールがコンポーネントを解析して、より直接的でアプリ固有のコードを生成します。
有用なメンタルモデル:
- ランタイム重視は、取扱説明書と工具箱を送って現地で組み立てるようなもの。
- コンパイル時は、部品が既に切り分けられてラベル付けされているので組み立てが速い。
実際のプロダクトは大抵その中間にあります。コンパイラ優先でもルーティング、データ取得、アニメーション、エラーハンドリングなどいくつかのランタイムコードは送られますし、ランタイム重視のフレームワークでもビルド時の手法(最小化、コード分割、サーバー描画)を利用してクライアントの負荷を減らします。重要なのはどちらが“正しい”かではなく、どのミックスが自分のプロダクトに合うかです。
Rich Harris と「フレームワークはコンパイラである」が重要な理由
Rich Harris はコンパイラ優先のフロントエンド思想を分かりやすく伝える人物の一人です。彼の主張はシンプルです:事前にできるだけ多くの作業をして、ユーザーがダウンロードするコードを減らし、ブラウザがやる仕事を減らす。
動機は実務的です。多くのランタイム重視のフレームワークは汎用エンジンを配布します:コンポーネントロジック、リアクティビティ、差分検出、スケジューリング、ヘルパー類など、あらゆるアプリに対応できるように作られています。その柔軟性はバイト数と CPU コストを生みます。UI が小さくても、大きなランタイム分のコストを払うことになります。
コンパイラアプローチはモデルを逆転させます。ビルド時にコンパイラが実際のコンポーネントを見て、そのコンポーネントに必要な DOM 更新コードを生成します。ラベルが決して変わらないならそれは普通の HTML になります。値が一つだけ変わるなら、その値だけを更新する経路が出力されます。汎用の UI マシンを配る代わりに、プロダクトに合わせた出力を配ります。
結果はたいてい単純です:ユーザーに送るフレームワークコードが少なくなり、各操作での処理量が減る。特に低スペック端末でその差が顕著になります。
トレードオフは残ります:
- コンパイラが事前に理解できない動的パターンは扱いにくい場合がある。
- ツール群の品質(ビルド速度、デバッグ、ソースマップ)がより重要になる。
- エコシステムの慣習が異なり、採用や再利用に影響することがある。
- 小さく特化したランタイムヘルパーは依然必要になることが多い。
実用的な目安としては、UI の多くがビルド時に予見可能であればコンパイラはタイトな出力を生成できます。UI が非常に動的でプラグイン駆動なら、より重いランタイムの方が楽なことが多いです。
コンパイル時最適化が実際に改善するもの
コンパイル時最適化は作業がどこで行われるかを変えます。より多くの決定がビルド時に下され、ブラウザに残る作業が減ります。
目に見える結果の一つは JavaScript の量が減ることです。バンドルが小さくなればネットワーク時間、解析時間、タップやクリックに反応するまでの遅延が減ります。ミッドレンジの端末ではこれが多くのチームが思うより重要になります。
コンパイラはより直接的な DOM 更新を生成できます。ビルド段階でコンポーネントの構造が見えると、実際に変わる DOM ノードだけを触る更新コードを出力し、各操作での抽象層を減らします。リストや表、フォームの頻繁な更新がよりスナッピーに感じられることがあります。
ビルド時の解析はツリーシェイクや未使用コード除去も強化します。効果は単にファイルが小さくなることだけでなく、ブラウザがロードして実行しなければならないコード経路自体が減る点にあります。
ハイドレーションもビルド時の選択が助ける領域です。サーバーでレンダリングされたページがインタラクティブになるために、どこにイベントを付けるか、どこまで状態を再構築するかが関係します。ビルドが何が本当にインタラクティブであるかをマークできれば、初回の作業を減らせます。
副次効果として、コンパイルは CSS スコーピングを改善することが多いです。ビルドがクラス名を書き換え、未使用のスタイルを削除し、コンポーネント間のスタイルリーケージを減らせます。これにより UI が成長しても予期せぬコストが減ります。
フィルタと大きなデータテーブルのあるダッシュボードを想像してください。コンパイラ優先のアプローチは初回読み込みを軽く保ち、フィルタ操作後に変更されたセルだけを更新し、決してインタラクティブにならない部分はハイドレートしないようにできます。
ランタイム重視のフレームワークが有効な場所
大きなランタイムが自動的に悪いわけではありません。しばしば柔軟性を買うことになります:実行時に決まるパターン、多くのサードパーティコンポーネント、長年テストされたワークフローなどです。
UI のルールが頻繁に変わる場合、ランタイム重視のフレームワークが強みを発揮します。複雑なルーティング、ネストされたレイアウト、リッチなフォーム、深い状態モデルが必要なら、成熟したランタイムはセーフティネットのように感じられるでしょう。
ランタイムは利便性に対する対価を支払っている
ランタイムが有効なのは、ビルド時だけでなく実行時にもフレームワークに多くを任せたいときです。それによりチームは日々速く動けることがあり、たとえオーバーヘッドが増えても価値があります。
一般的なメリット:大きなエコシステム、状態管理やデータ取得の慣れたパターン、優れた開発ツール、プラグイン的拡張のしやすさ、採用時の学習コストの低さなど。
チームの慣れは実際のコストであり利益でもあります。わずかに遅いフレームワークでも、チームが自信を持って出荷できるなら、より速くても再教育やカスタムツールを要するアプローチに勝ることがあります。
ランタイムオーバーヘッドがボトルネックでない場合
多くの「遅いアプリ」の不満はフレームワークのランタイムが原因ではありません。ページが遅い API、重い画像、多数のフォント、外部スクリプトを待っているなら、フレームワークを変えても核心は直りません。
ログイン後の社内管理ダッシュボードは、大きなランタイムでも問題なく感じられることがよくあります。ユーザーは強い端末を使い、作業はテーブルや権限、バックエンドのクエリが支配するためです。
初期は「十分に速い」を目標にするのが正しいこともあります。プロダクトの価値をまだ検証している段階なら、反復速度を高く保ち、基本的な予算を設定し、コンパイラ優先の複雑さは実際に意味があると証明されたときに導入してください。
チームにとっての本当のトレードオフ:パフォーマンス対反復速度
反復速度とはフィードバックまでの時間:画面を変えて、実行して、何が壊れたか見て、修正する速さです。ループが短いチームはより頻繁に出荷して早く学べます。これがランタイム重視のフレームワークが初期に生産的に感じられる理由です:慣れたパターン、迅速な結果、多くの組み込み動作。
パフォーマンス作業は早すぎたり範囲が広すぎたりするとそのループを遅くします。もし全てのプルリクがマイクロ最適化の議論になると、チームはリスクを取らなくなります。プロダクトが何であるかを知らないうちに複雑なパイプラインを構築すると、ツール同士で戦うことに時間を使い、ユーザーとの対話が減ります。
コツは「十分に良い」の定義に合意し、その枠内で反復することです。パフォーマンス予算があればその枠ができます。目指すのは完璧なスコアではなく、開発を止めずに体験を守るための制約です。
実用的な予算例:
- ミッドレンジ端末で数秒以内にページが使える状態になること。
- ルートごとの JavaScript をチームが合意した上限に収めること。
- 主要な操作(検索、カート追加、保存)が迅速に応答すること。
- 単一機能が追加できる JS/CSS の量に小さな上限を設けること。
無視すると代償を払うことが多いです。プロダクトが成長すると、遅さは小さな調整の問題ではなくアーキテクチャの問題になり、遅い段階での書き換えは機能凍結、再訓練、これまで動いていたワークフローの破壊を意味します。
コンパイラ優先のツールはこのトレードオフを変えることがあります。ビルド時間が少し長くなることを受け入れる代わりに、各訪問ごとに端末がやる作業を減らせます。
予算はプロダクトの成長に合わせて見直してください。初期は基礎を守り、トラフィックと収益が増えたら予算を厳しくして、実際の指標に効く箇所に投資しましょう。
迷わないための計測方法
「速い」の定義で揉めると議論は泥沼になります。少数の指標を選んで書き出し、共有スコアボードとして扱ってください。
シンプルなスターターセット:
- 初回読み込み時間(最初に使える画面までの時間)
- 操作遅延(タップやクリックがもたつく時間)
- バンドルサイズ(ブラウザがダウンロードして解析する量)
- API レイテンシ(バックエンドの応答時間)
開発用ラップトップだけでなく代表的なデバイスで測定してください。高速 CPU、ウォームキャッシュ、ローカルサーバはミッドレンジ端末や平均的なモバイル回線で出る遅延を隠してしまいます。
現実に即して:実際のユーザーに近い 2〜3 台のデバイスを選び、毎回同じフロー(ホーム、ログイン、よく使うタスク)を実行して比較します。
フレームワークを変える前にベースラインを取ってください。今日のビルドを使い、同じフローを記録して「ビフォー写真」を残します。
ラボのスコアだけで判断しないでください。ラボは初回読み込みを高く評価する一方で、ユーザーが不満に思う点(メニューのカクつき、入力の遅延、最初の画面以降の遅延)を見逃すことがあります。
数値が悪化したら推測せずに、何が出荷されたか、何がレンダリングをブロックしているか、時間がどこに使われたか(ネットワーク、JavaScript、API)を確認してください。
実際に使える意思決定フレームワーク
落ち着いて再現可能な選択をするには、フレームワークとレンダリングの決定をプロダクトの意思決定のように扱ってください。目標は最先端の技術ではなく、パフォーマンスとチームの速度の適切なバランスです。
- プロダクトのサーフェスを定義する。 マーケティングページ、ログイン後のダッシュボード、モバイル、内部管理ツールに分け、それぞれのユーザー、デバイス、期待を整理します。
- パフォーマンス予算を設定する。 ミッドレンジ端末での最初の読み込みや主要操作の時間など、守るべき 2〜3 の数値を決めます。
- 制約を順位付けする。 今何が最も重要か正直に評価します:チームのスキル、期限、SEO、データ保護ルール、どこでアプリを動かす必要があるか等。
- アプローチを選ぶ。 初回読み込みと操作速度が絶対に必要ならコンパイラ優先。最大の柔軟性と速い反復が必要ならランタイム優先。多くの場合ハイブリッドが勝ちます:パフォーマンスが重要なサーフェスは軽く保ち、インタラクティブ性が価値を生む箇所では重めのランタイムを使う。
- Thin slice で検証して固定する。 主要なユーザーフローを一つエンドツーエンドで作り、予算に照らして計測し、何を選んだか理由を書き残します。
Thin slice には実際の面倒な部分(実データ、認証、最も遅い画面)を含めるべきです。
Thin slice を素早く試作するための手段として、Koder.ai(koder.ai)はチャット経由で Web、バックエンド、モバイルのフローを構築し、ソースコードをエクスポートできます。実際のルートを早期に検証して、実験をスナップショットやロールバックで可逆に保つのに役立ちます。
選択は書き残し、いつ見直すか(トラフィック増、モバイル比率、SEO 目標)を明記してチームが変わっても決定が持続するようにしましょう。
チームがパフォーマンス選択で犯しがちなミス
多くの場合、チームは今日見えていることを最適化してしまいがちで、3 ヶ月後にユーザーが感じることを見落とします。
一つ目のミスは最初の週に過度に最適化することです。日に変わるページを数ミリ秒削るために数日費やし、本質的にはユーザーが求める機能がまだ整っていないことがあります。初期は学習を速めることを優先し、ルートやコンポーネントが安定してから深い最適化を行いましょう。
もう一つはバンドル成長を放置することです。200 KB で問題なく見えても、いくつかの「小さな」追加で数メガバイトになり得ます。簡単な習慣として、バンドルサイズを時間で追跡し、急激な増加をバグとして扱うとよいです。
チームがすべてをクライアント側レンダリングに任せることもありますが、価格ページやドキュメント、オンボーディングなど静的なルートは別にしておくべきです。そうしたページは端末側の作業を大幅に減らして配信できます。
便利さのために大きな UI ライブラリを入れて、その本番ビルドでのコストを測っていないことも静かな殺し文句です。利便性は正当ですが、追加される JavaScript・CSS・ミッドレンジ端末での遅い操作の代償を明確にしてください。
最後に、境界を明確にしないでアプローチを混ぜるとデバッグが難しいアプリになります。アプリの半分がコンパイラ生成の更新を前提にし、残りがランタイムマジックを前提にしていると、ルールが曖昧になり失敗が分かりにくくなります。
現場で効くガードレール:
- 主要なルートごとにバンドルサイズの予算を設定する。
- 初回読み込みが速くあるべきルートを決め、それに合わせたレンダリング設計にする。
- ライブラリは本番での重さを確認してから追加する。
- どの部分がコンパイラ最適化され、どの部分がランタイムか境界を書面化する。
- 実際のユーザーデータを得てから決定を見直す。
現実的な例:SaaS を出荷して身動きが取れなくならない方法
3 人チームでスケジューリングと請求の SaaS を作ることを想像してください。顔は二つ:公開マーケティングサイト(ランディング、価格、ドキュメント)と認証後ダッシュボード(カレンダー、請求書、レポート、設定)。
ランタイム優先の道を選べば、UI の変更が速くなるためダッシュボードは大きなクライアントサイドアプリになり、再利用可能なコンポーネント、状態ライブラリ、リッチなインタラクションを備えます。反復は速いですが、時間が経つとミッドレンジ端末での初回読み込みが重く感じられ始めるかもしれません。
コンパイラ優先を選べば、クライアントの JavaScript を減らして一般的なフロー(ダッシュボードを開く、タブ切替、検索)がよりスナッピーに感じられるようになります。代償としてチームはパターンやツールに対してより慎重になる必要があり、一部のランタイムトリックがすぐには使えないこともあります。
変化を促すのは好みではなく現実です:遅いページがサインアップを減らす、低スペック端末の利用者が増える、エンタープライズ顧客が予算の可視化を求める、ダッシュボードが常時開かれるようになってメモリが問題になる、サポートに遅さの報告が増える──こうした実務的な圧力です。
多くの場合ハイブリッドが勝ちます。マーケティングページは軽く保ち(サーバレンダリングやほぼ静的、最小限のクライアントコード)、インタラクティビティで価値が出るダッシュボードにはより重いランタイムを受け入れる。
意思決定手順を使うと:重要な経路(サインアップ、最初の請求、週次レポート)を名付け、ミッドレンジ端末で測定し、予算を設け、ハイブリッドを選びます。公開ページと共通コンポーネントはコンパイラ優先、実験速度が重要な箇所のみランタイム重視にする、といった具合です。
あなたのプロダクトに向けたクイックチェックリストと次の一手
これらのアイデアを実践に落とす最も簡単な方法は短い週次ループを回すことです。
15 分のスキャンから始めてください:バンドルサイズは増えているか、どのルートが遅いと感じられるか、そのルートで大きな UI 要素は何か(テーブル、チャート、エディタ、地図)、どの依存関係が重いかを確認します。それからリライトなしで直せるボトルネックを一つ選びます。
今週やることは小さく:
- ベースラインを 1 つ計測:最も遅いルートのコールドロードと一般的な操作 1 つ。
- 自分のプロダクトに合った予算を 1 つ設定。
- ボトルネックを 1 つ直す(依存を削除、ルート分割、重いコンポーネントの遅延読み込み、不要なレンダリングの削減)。
- 再測定して何が変わったかを書き残す。
選択を可逆に保つには、ルートや機能の境界を明確に描いてください。後で差し替えやすいモジュール(チャート、リッチテキストエディタ、分析 SDK など)を優先すると良いです。
よくある質問
自分の端末は速いのにウェブアプリが遅く感じるのはなぜ?
多くの場合、ネットワークだけが原因ではありません。ランタイムコスト、つまりブラウザが JavaScript をダウンロード、解析、実行して UI を組み立て、更新ごとに追加作業をする負担が主な原因です。
そのため、JavaScript の負荷が大きくなると、良いラップトップでもアプリが「重く」感じられることがあります。
ランタイム重視のフレームワークとコンパイル時最適化の違いは?
目標は同じ(クライアントでの処理を減らす)ですが、仕組みが異なります。
- ランタイム重視: ブラウザ側で更新を判断する汎用エンジンを配布します。
- コンパイル時: ビルド時にコンポーネントを解析してアプリ固有の更新コードを生成し、ブラウザ側のロジックを減らします。
「フレームワークはコンパイラだ」とは具体的に何を意味する?
フレームワークがビルド時にコンポーネントを解析して、汎用の UI エンジンではなくアプリ向けに最適化されたコードを出力する、という意味です。
実務的な利点は、通常バンドルが小さくなり、操作中(クリック、入力、スクロール)に使う CPU 作業が減ることです。
圧倒されずに追いかけるべき性能指標は何?
まずは次を追いかけてください:
- 最初に使える画面までの時間(単なる「ページ読み込み」ではない)
- 操作遅延(タップやクリックがもたつかないか)
- ルートごとの JavaScript サイズ(各画面がブラウザにダウンロード・解析させる量)
- API レイテンシ(バックエンドの応答時間)
毎回同じユーザーフローで測定することでビルド間を比較できます。
フレームワークを変えれば遅いアプリは直るの?
場合によります。遅さの原因が遅い API、重い画像、多数のフォント、外部スクリプトなら、フレームワークを変えても根本は直りません。
フレームワーク選択は手段の一つです。最初にどこに時間がかかっているか(ネットワーク、JS の CPU、レンダリング、バックエンド)を確認してください。
ランタイム重視のフレームワークが依然として有効なのはどんなとき?
柔軟性と反復速度が求められる場合はランタイム重視が向きます:
- 多数のサードパーティコンポーネント
- 複雑なルーティングやネストされたレイアウト
- プラグイン的な拡張点
- チームがそのエコシステムに慣れている
ランタイムがボトルネックでないなら、利便性の対価として数バイト増える価値は十分にあります。
コンパイラ優先、ランタイム優先、ハイブリッドのどれを選べば良い?
簡単なデフォルトは次の通り:
- コンパイラ優先:ミッドレンジ端末での初回読み込みと操作速度が重要なルート(ランディング、オンボーディング、主要なフロー)。
- ランタイム重視:最大限の柔軟性が必要な箇所(複雑なダッシュボード、社内ツール)。
ハイブリッドは実務的に最も有効で、境界を明文化してアプリがごちゃ混ぜにならないようにします。
実チーム向けの実用的なパフォーマンス予算はどんなもの?
ユーザー感覚を守りつつ開発を止めない予算を使いましょう。例:
- ミッドレンジ端末で数秒以内に最初の画面が使えること
- ルートごとの JavaScript に上限を設ける
- 検索・フィルタ・保存など主要操作は素早く反応する
- 単一機能が追加できる JS/CSS の量に上限を設ける
予算は制約であり、完璧さを競うためのものではありません。
ハイドレーションとは何で、なぜ初回読み込みが遅く感じるの?
ハイドレーションとは、サーバーでレンダリングされたページにイベントハンドラを付け、ブラウザ側で十分な状態を再構築してインタラクティブにする作業のことです。
ハイドレーションしすぎると、HTML は早く見えても最初の操作が重く感じられることがあります。ビルド時の分析で本当にインタラクティブにする必要がある部分だけを指定できれば、初回の負荷を減らせます。
フレームワーク選択にコミットする前に Thin-slice プロトタイプに含めるべきものは?
コミット前に試すべき「Thin Slice」は次を含むべきです:
- 認証/ログイン
- 実データと遅い API 呼び出し
- 最も重い画面(テーブル、フォーム、ダッシュボード)
- ユーザーが繰り返す主要な操作(フィルタ、保存、検索)
プロトタイプ作成に Koder.ai(koder.ai)を使えば、チャット経由で Web+バックエンドフローを作り、ソースをエクスポートできるので、フル書き換えせずに早期に比較できます。