- SPAは遅いのではなく「描画の開始が遅れる」
- Critical Rendering Pathとは何か
- 通常のWebページの場合
- SPAの場合に起きること
- なぜLCPが遅くなるのか
- CSSブロッキングとの組み合わせ
- よくある誤解:JSが重いから遅い?
- 改善アプローチ
- 注意点:Skeleton UIの罠
- 結局どう考えるべきか
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が描画を待たせない構造を作ることです。