SSRでCLSが起きる理由 ― フォントロードとレイアウトシフト

HTMLが早く出るほどレイアウトは揺れやすくなる

SSR(Server Side Rendering)は初期表示が速く、LCP改善に効果があると説明されることが多いです。
しかし運用を始めると、別の指標が悪化するケースがあります。CLS(Cumulative Layout Shift)です。

「表示は速くなったのにスコアが下がった」
この現象は珍しくありません。原因はレンダリングの失敗ではなく、表示が早すぎることにあります。

SSRはHTMLを即表示できるため、ブラウザが「未確定のレイアウト」を先に描画します。その後、リソースが揃った瞬間に再配置が発生し、レイアウトシフトが記録されます。

CLSとは何を測っているのか

CLSは単なる再描画回数ではありません。
「ユーザーが見ている間に要素の位置がどれだけ動いたか」を測定します。

例:

  • ボタンが下にズレる
  • 見出しが押し出される
  • 画像の高さが変わる

重要なのは「視覚的な移動」です。
表示が速くても、配置が変わるとCLSは悪化します。

SSRでなぜ発生しやすいのか

SSRでは、HTML到着直後にブラウザはレイアウトを計算します。
しかしこの時点では次の情報が不足しています。

  • Webフォントの幅
  • 画像サイズ
  • 非同期CSS

つまりブラウザは「仮のサイズ」で描画します。

後からリソースが到着すると、実際のサイズに合わせて再計算が行われます。
これがレイアウトシフトです。

SPAではJS実行後にまとめて描画されるため、初回配置が確定しやすく、逆にCLSが出にくい場合があります。

フォントロードが最大の原因になる

特に影響が大きいのがWebフォントです。
フォントが到着するまで、ブラウザは代替フォントを使用します。

流れ:

1. SSR HTML描画(代替フォント)
2. Webフォント取得
3. フォント置換
4. 文字幅が変化
5. 行折返し変更
6. レイアウト移動

これをFOUT(Flash of Unstyled Text)と呼びます。
文字の幅が変わるため、段落全体が上下に動き、CLSが計測されます。

画像も同じ問題を起こす

画像も同様です。
HTMLにサイズ指定がない場合、ブラウザは高さを0として扱います。

<img src="/hero.jpg">

画像読み込み後:

  • 高さが確定
  • 下のコンテンツが押し下げられる

これもCLSになります。

なぜLCPとトレードオフになるのか

LCPを改善するには「早く表示」する必要があります。
しかし早く表示すると、リソース未確定状態で描画が始まります。

結果:

  • LCP改善
  • CLS悪化

つまり同時最適化が難しい指標です。

対策:フォント表示戦略

有効なのはフォント表示方法の制御です。

@font-face {
  font-family: "MyFont";
  src: url("/font.woff2") format("woff2");
  font-display: swap;
}

font-displayの意味:

挙動
swap 代替フォント→置換
block 表示待機
optional 取得失敗時は置換しない

CLSを抑えるには、フォントサイズ差の少ない代替フォントを選ぶことも重要です。

対策:画像のサイズ指定

画像は必ずサイズを指定します。

<img src="/hero.jpg" width="1200" height="630">

これによりブラウザはレイアウトを確定できます。
読み込み後も位置は変わりません。

もう一つの原因:非同期CSS

遅延ロードされたCSSもレイアウトシフトを引き起こします。
特にファーストビューのスタイルを後から適用すると、大きく揺れます。

critical CSSをインライン化すると改善します。

結局なぜ起きるのか

SSR
「未完成でも表示する」

SPA
「完成してから表示する」

この差がCLSの差です。
SSRはユーザーに早く見せる代わりに、レイアウト確定前の画面を見せてしまいます。

CLS対策は難しい最適化ではありません。
ブラウザが最初に計算したレイアウトを変えないことです。

  • フォント幅を揃える
  • 画像サイズを指定する
  • 重要CSSを先に出す

SSRは表示を早める技術ですが、完成度の低い画面を出しやすい技術でもあります。
表示速度の最適化は「速さ」だけでは完結しません。
「最初の1回のレイアウトをどれだけ安定させるか」が、最終的なユーザー体験を決めます。