SPAでLCPが悪化しやすい理由をCritical Rendering Pathから解説

SPAは遅いのではなく「描画の開始が遅れる」

SPA(Single Page Application)は操作が滑らかで高速な体験を提供できますが、初回表示の評価指標であるLCP(Largest Contentful Paint)が悪化しやすい傾向があります。
原因はレンダリング能力ではありません。描画開始のタイミングが遅れることです。

この現象はCritical Rendering Path(クリティカルレンダリングパス)を理解すると明確になります。

Critical Rendering Pathとは何か

ブラウザが画面を表示するまでには、決まった手順があります。

1. HTML解析(DOM生成)
2. CSS解析(CSSOM生成)
3. Render Tree生成
4. レイアウト計算
5. ペイント

この流れをCritical Rendering Pathと呼びます。
重要なのは、HTMLとCSSが揃うまで描画は始まらない点です。

通常のWebページの場合

SSRや静的HTMLページでは、サーバから次のようなHTMLが返ります。

<h1>記事タイトル</h1>
<p>本文...</p>
<img src="/hero.jpg">

ブラウザは受信と同時にDOMを作り、CSSが届けばすぐ描画できます。
ヒーロー画像が表示されると、その瞬間がLCPとして計測されます。

つまりネットワーク待ちが終わると描画が始まります。

SPAの場合に起きること

SPAの初回レスポンスは次のようになります。

<div id="app"></div>
<script src="/main.js"></script>

ここにコンテンツは存在しません。
ブラウザはDOMを作りますが、表示する要素がありません。

ここから次の処理が追加されます。

1. JSダウンロード
2. JS解析
3. JS実行
4. API通信
5. DOM生成
6. 描画

つまりCritical Rendering Pathの前に「JavaScript実行」が挿入されています。
これがLCP悪化の本質です。

なぜLCPが遅くなるのか

LCPは「最大要素が描画された時刻」です。
SPAでは最大要素(記事本文や画像)がDOMに追加されるのが最後です。

つまり:

  • HTML到着 → 何も表示されない
  • JS実行 → 準備
  • API応答 → DOM生成
  • 画像取得 → 初めて表示

ここで初めてLCPが計測されます。
HTML受信から数秒後になることも珍しくありません。

CSSブロッキングとの組み合わせ

さらに悪化するのがCSSです。
SPAではCSSもJSバンドルに含まれる場合があります。

その場合:

  • JS実行
  • CSS挿入
  • 再レイアウト
  • 描画

となり、描画がさらに後ろへ移動します。
Critical Rendering Pathが長くなる形です。

よくある誤解:JSが重いから遅い?

JSサイズは一因ですが、本質ではありません。
重要なのはJSが描画の前に必須になっていることです。

SSR
HTML → 描画 → JS

SPA
JS → HTML生成 → 描画

この順序の違いがLCPを左右します。

改善アプローチ

SPAでLCPを改善するには、描画をJSから切り離します。

代表的な方法:

  • SSR
  • Prerendering
  • 重要部分のみHTML化
  • 画像のpreload

特にヒーロー画像はpreloadが有効です。

<link rel="preload" as="image" href="/hero.jpg">

これにより画像取得が前倒しされます。

注意点:Skeleton UIの罠

ローディングスケルトンを表示すると速く見えますが、LCPは改善しない場合があります。
理由は、スケルトンは最大要素ではないためです。
本物のコンテンツが表示された瞬間がLCPとして計測されます。

つまり体感速度と指標が一致しないことがあります。

結局どう考えるべきか

LCPは描画速度の指標ではありません。
「いつ本物の内容が表示されたか」の指標です。

SPAは操作性に優れますが、初期表示ではJSがレンダリングパスに入り込みます。
これがCritical Rendering Pathを延ばし、LCPを遅らせます。

パフォーマンス改善は軽量化だけでは達成できません。
描画が始まる順序を設計することが重要です。
SPAの最適化とは、JavaScriptを減らすことではなく、JavaScriptが描画を待たせない構造を作ることです。