Claude Media
Claude CodeでCLS(レイアウトシフト)の原因を探して直す手順

Claude CodeでCLS(レイアウトシフト)の原因を探して直す手順

Claude CodeのChrome連携で表示の崩れを観察し、画像サイズ・フォント・広告枠の3原因をwidth/height、font-display、min-heightで直して、修正前後を比べる手順です。

CLS(Cumulative Layout Shift)は、読み込み中や読み込み後にページの中身が予告なく動いた量を数える指標です。Claude CodeにChrome連携をつなぐと、実際にページを開かせて「どの要素が動いたか」を観察させ、そのままCSSやHTMLの修正まで任せられます。この記事では、画像サイズ・Webフォント・広告枠の3原因を題材に、再現から修正、修正前後の比較までを順に示します。

CLSは何を数え、スクショでどこまで見えるか

CLSは、ページの生存期間に起きた予期しないレイアウトシフトのうち、最大の「バースト」のスコアです。バーストとは、各シフトの間隔が1秒未満で、全体が最長5秒に収まる連なりを指します。

目安は次のとおりです。

数字

CLSの判定基準

  • 良好

    0.1以下

    モバイルとデスクトップを分けて測る

  • 不良

    0.25超

    間の値は要改善

75パーセンタイルで評価

1回のシフトのスコアは「影響範囲 × 移動距離」です。画面の半分を占める要素が、画面の高さの25%だけ下がった場合を考えます。移動前後の領域を合わせると画面の75%なので影響範囲は0.75、移動距離は0.25です。スコアは0.75 × 0.25 = 0.1875になり、この1回だけで「不良」の線を越えます。

ここで押さえたいのは、スクリーンショットの限界です。静止画は撮った瞬間の状態しか写しません。動いた事実は「前後2枚の差」としてしか見えず、スコアも出ません。

そこで役割を分けます。

  • スクリーンショットとGIF: 何がどう動いたかを人間が見て確かめる
  • 計測コード(Layout Instability API)やLighthouse: 何点動いたかを数字で出す

この2本立てにすると、画面比較の弱点を数字が補い、数字の弱点(どこが悪いか分からない)を画面が補います。

Chrome連携を用意する

ブラウザ操作はClaude in Chrome拡張と組み合わせて行います。必要なものは、Chrome・Edgeなど対応ブラウザ、拡張のバージョン1.0.36以上、Claude Code、そしてPro・Max・Team・Enterpriseのいずれかのプランです。WSLでは使えません。APIキーやclaude setup-tokenのトークンで認証しているときは、--chromeを付けても連携が有効になりません。/loginで入り直してください。

claude --chrome

起動後に/chromeを実行すると、接続状態が確認できます。「Status: Enabled」と「Extension: Installed」が並べば準備完了です。毎回フラグを付けたくない場合は、同じ画面の「Enabled by default」で既定にできます。ただしブラウザツールの説明が常にコンテキストへ載るため、使用量が増えます。起動時の既定値の切り替えはClaude Codeの起動時の既定を切り替える3つのキーで扱っています。

Claudeは新しいタブを開いて作業し、Chromeのログイン状態を共有します。ローカルの開発サーバー(localhost:3000など)を開いて確かめさせる使い方が、ドキュメントの例にも載っています。画像は「Take a screenshot ... and save it to disk」と頼むとファイルに保存され、パスが返ります。操作の流れはGIFでも記録できます。GIFには画面に見えているものがすべて写るので、共有前にログイン情報が入っていないか見直してください。

実験用ページで3つの原因を再現する

本番サイトをいきなり測ると、原因が混ざって切り分けにくくなります。まず、3原因をわざと仕込んだ小さなページを用意します。

<!doctype html>
<html lang="ja"><head><meta charset="utf-8">
<style>
  @font-face {
    font-family: "Demo";
    src: url("/fonts/demo.woff2") format("woff2");
    font-display: swap;
  }
  body { font-family: "Demo"; max-width: 640px; margin: 0 auto; }
  img  { max-width: 100%; height: auto; }
</style></head>
<body>
  <h1>記事タイトル</h1>
  <div id="ad"></div>
  <img src="/img/hero.jpg" alt="">
  <p>本文が続きます。</p>
  <script>
    setTimeout(() => {
      document.getElementById("ad").innerHTML =
        '<div style="height:250px;background:#eee">広告</div>';
    }, 1500);
  </script>
</body></html>

仕込んだ問題は3つです。

  • <img>にwidthとheightがない。読み込み完了までは高さ0で、完了すると本文が押し下げられる
  • font-display: swapで、フォールバックフォントの指定もない。入れ替えの瞬間に文字の幅と行の高さが変わる
  • 1.5秒後に広告枠を差し込む。枠の高さを確保していないので、後続の要素がまとめて下がる

広告は実物ではなくsetTimeoutで擬似的に遅延させています。本物の広告タグは配信側の都合で挙動が変わり、検証が安定しないためです。

Claudeに観察させる

最初の依頼は「直して」ではなく「観察して」にします。原因が分からないまま直させると、関係ない箇所まで触られます。

localhost:3000 を開き、読み込み中から読み込み完了までの
画面を順に観察して、表示位置が動いた要素を挙げてください。
動いた順に、動く直前と直後のスクリーンショットをディスクに保存し、
ファイル名を一覧にしてください。まだコードは直さないでください。

こうした依頼の返答は、例えば「見出しの下で本文が2回下がった」といった形になります。スクリーンショットの撮影タイミングは読み込みの速さに左右されます。写っていなければ、広告枠の遅延を3秒に延ばすなど、再現条件を調整して撮り直させます。

数字も取ります。Layout Instability APIのlayout-shiftエントリを記録するコードは、次のとおりです。DevToolsのコンソールに貼れば、Claudeを介さずに確かめられます。

new PerformanceObserver((list) => {
  for (const e of list.getEntries()) {
    if (e.hadRecentInput) continue;      // 操作直後のシフトは除外
    console.log("shift", e.value.toFixed(4), e.sources?.map(s => s.node));
  }
}).observe({ type: "layout-shift", buffered: true });

hadRecentInputは、ユーザー操作から500ミリ秒以内のシフトに付く印です。これらはCLSから除かれるため、同じ条件で数えるには外す必要があります。なお、Claudeがページ内でスクリプトを実行できるかは、/mcpでclaude-in-chromeを選び「View tools」から確かめられます。実行できる場合は、このコードを渡して結果を読ませる使い方もできます。

Lighthouseを使う場合は、コマンドラインから結果をJSONで残せます。

npx lighthouse http://localhost:3000 \
  --only-categories=performance \
  --output=json --output-path=./lh-before.json

Lighthouseには、幅と高さのない画像を挙げる監査と、シフトした要素を一覧にする「Avoid large layout shifts」があります。ただし挙がるのは「動かされた側」で、原因の要素とは限りません。上に新しい要素が差し込まれると、その下の要素が全部並びます。スクリーンショットの前後比較は、この「動かした側」を探すのに効きます。Lighthouse CIを編集後の自動チェックに組み込む方法はClaude CodeでLighthouse CIを自動実行する記事にあります。

原因1: 画像に寸法がない

画像の高さは、ダウンロードして実寸が分かるまで確保されません。対策は、<img>と<video>にwidthとheightの属性を付けることです。ブラウザはその値から縦横比を計算し、読み込み前から枠を確保します。

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

CSSのimg { max-width: 100%; height: auto; }はそのままで構いません。属性は表示サイズの指定ではなく、縦横比を伝える役目を担います。aspect-ratioをCSSで直接指定しても、同じ効果が得られます。

レスポンシブ画像でsrcsetを使う場合は、候補の画像すべてを同じ縦横比にします。画面幅で切り抜きを変える<picture>では、<source>にもwidthとheightを付けます。

Claudeへの依頼は次の形です。

寸法属性のない <img> と <video> をリポジトリ全体から探し、
実ファイルの縦横ピクセルを読み取って width と height を追加してください。
srcset を使っている画像は、候補の縦横比が揃っているかも報告してください。

実寸は、画面上で測らせず、画像ファイルそのものから読み取らせてください。

原因2: Webフォントの入れ替え

フォントの読み込み中は、2通りの表示があります。フォールバックで先に表示して後で入れ替えるか、読み込みまで文字を隠すかです。どちらもテキストは代替フォントの寸法で配置済みなので、入れ替わった瞬間にブロックが動きます。

font-displayの値ごとの違いは次の表のとおりです。

値読み込み前の表示後から入れ替えCLSへの効き方
block読み込み前の表示短い間は文字が見えない後から入れ替えするCLSへの効き方入れ替え時に動く
swap読み込み前の表示すぐフォールバックで表示後から入れ替えするCLSへの効き方入れ替え時に動く
fallback読み込み前の表示すぐフォールバックで表示後から入れ替え短い期間だけするCLSへの効き方間に合わなければ動かない
optional読み込み前の表示すぐフォールバックで表示後から入れ替えしないCLSへの効き方初回描画に間に合わなければ動かない

swapはテキストが読める点では良い選択ですが、CLSを下げる値ではありません。シフトを避けたいならoptionalが有力です。代わりに、初回表示で間に合わなかったフォントはその訪問では使われません。

もう一つの手は、フォールバックを本物のフォントに近づけることです。@font-faceのsize-adjust・ascent-override・descent-override・line-gap-overrideで、代替フォントの寸法を合わせられます。

@font-face {
  font-family: "Demo Fallback";
  src: local("Arial");
  size-adjust: 105%;          /* 数値は例。実測して決める */
  ascent-override: 92%;
  descent-override: 24%;
}
body { font-family: "Demo", "Demo Fallback", sans-serif; }

フォールバックを指定しないと、Chromeでは既定のTimes系フォントが使われ、本物との差が大きくなります。最低でもsans-serifなどの総称を並べてください。重要なフォントは<link rel="preload">で先に取りにいくと、初回描画に間に合う可能性が上がります。

数値の決め方も、Claudeに任せられます。フォントを読み込む前後の同じ段落の高さをブラウザ上で測り、差が小さくなるまでsize-adjustを調整させる流れです。測るたびにスクリーンショットを保存させれば、どの値でどれだけ差が縮んだかを後から見返せます。

原因3: 広告と遅れて差し込まれる要素

広告・埋め込み・Cookieバナーは、読み込みが終わってから高さが決まります。対策は、初期レイアウトで場所を確保することです。

#ad { min-height: 250px; }

高さが固定でない広告は、よく出るサイズ、または最小のサイズに合わせてmin-heightを置きます。大きい広告が出たときのシフトは残りますが、高さ0から伸びるよりは小さく済みます。逆に、広告が返らなかったときに枠を畳むと、その分のシフトが起きます。枠は畳まず、プレースホルダを表示し続けてください。

差し込む位置も効きます。画面の上のほうに入れるほど動く範囲が広がるので、ビューポートの下側に置くと影響が小さくなります。ページの上下から出るバナーは、ドキュメントの流れから外して重ねる形(position: fixedなど)にすると、周りの要素を押しません。

「もっと読む」のようにユーザーの操作で内容を足す場合は、操作から500ミリ秒以内のシフトは数えられません。取得に時間がかかるなら、先に枠を確保して中身を後から入れてください。

アニメーションにも注意が要ります。topやleftの変更はシフトとして数えられます。動かすならtransformを使います。

修正前後を比べて確かめる

直す対象は、1回に1原因です。3つ同時に直すと、どれが効いたか分からなくなります。

手順

1原因ずつ直して比べる流れ

  1. 1

    修正前を保存する

    観察の依頼で保存したスクリーンショットと、lh-before.jsonを残します。

  2. 2

    1つだけ直す

    例えば画像のwidthとheightだけを追加します。ほかのファイルは触らせません。

  3. 3

    同じ条件で撮り直す

    読み込みの遅延、画面幅、開くURLを前回と揃えます。

  4. 4

    数字と画像で比べる

    lh-after.jsonのスコアと、前後のスクリーンショットを並べます。

比較の依頼は次のとおりです。

before と after のスクリーンショットを見比べて、
動かなくなった要素と、まだ動いている要素を分けてください。
lh-before.json と lh-after.json の CLS の差も添えてください。

注意点が一つあります。ローカルの開発サーバーと本番では、画像が手元にキャッシュされている、APIが速すぎる、広告が出ない、といった違いがあります。ローカルで0になっても、本番の値は別です。本番の数字は、実ユーザーのデータ(CrUX)で見ます。ラボ計測は読み込み時のシフトが中心ですが、実ユーザーのCLSはページの生存期間全体が対象です。スクロール中に遅延読み込みの画像が入るなど、読み込み後のシフトは両者の差になって表れます。

ブラウザ操作の別の道具として、DevToolsの計測を直接扱うChrome DevTools MCPもあります。Chrome連携が「実際のログイン状態のChromeを操作する」のに対し、こちらはトレースやLighthouse監査の取得に向きます。

つまずきやすい点

  • ブラウザが応答しない: ページにalertなどのダイアログが出ていると、操作が止まります。ダイアログを閉じて、続行を伝えてください
  • 「Receiving end does not exist」: 拡張のサービスワーカーが休止しています。/chromeから「Reconnect extension」を選びます
  • 「Browser extension is not connected」: 拡張が別のclaude.aiアカウントや組織にログインしている可能性があります。/statusのOrganization行と、拡張側の組織を揃えてください
  • プロジェクトの設定でChromeを有効にできない: .claude/settings.jsonのenvにCLAUDE_CODE_ENABLE_CFCを書いても無視されます。リポジトリ側が勝手にブラウザへつなげないための仕様で、有効にするにはclaude --chromeか/chromeを使います
  • 値が毎回違う: 広告やフォントの読み込み時間がぶれているためです。比較の条件(遅延時間・画面幅)を固定してください

まとめ

CLSの修正は、画像の寸法、フォントの入れ替え、後から入る要素の3つに分けて考えると進めやすくなります。スクリーンショットは「何が動いたか」を示し、計測は「どれだけ動いたか」を示すので、片方だけでは判断を誤ります。直すときは原因を1つずつ、同じ条件で前後を撮ると、効いた修正が分かります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/chrome
  2. web.dev/articles/cls
  3. web.dev/articles/optimize-cls
  4. developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
  5. github.com/GoogleChrome/lighthouse
この記事を共有:XはてブLinkedIn