LCPは3.1秒で良好、なのにSpeed Indexは44.7秒。
WordPressで作られた建設PM/CM会社のコーポレートサイト(1,268ページ)を外から実測すると、ページ総重量32.4MBのうち37%を自前配信の日本語Webフォント(12.1MB)が占めていました。
この記事では、静的HTMLだけを見て判断を誤らないように、ブラウザ描画後のDOMとネットワークを実測した手順をまとめます。サブセット化すると約1/8になることも推測ではなく実測で示し、誤検出を潰した過程も載せます。
前提
診断の対象と、使った道具は以下のとおりです。
- 対象: 見込み客のコーポレートサイト(WordPress・sitemap全303ファイル・1,268URL)
- 速度: Lighthouse を3回実行し、その中央値を採用
- DOM: Playwright(Python)でブラウザ描画後のDOMを取得
- セキュリティ: SSL Labs・Observatory
- 網羅: sitemap全303ファイルを巡回して1,268URLを収集
- 実データ: PSI(PageSpeed Insights・CrUX)を試みたが取得できず(取得できなかった経緯は「落とし穴」の章に書きます)
curl や requests で取った静的HTMLは補助に使うだけにしました。画像の枚数、リンクの有無、レイアウトの判定は、すべてブラウザ描画後の状態で確かめています。
手順1: 数字のねじれを見つける
Lighthouse 3回の中央値で、以下の2つの数字が並びました。
| 指標 | 値 |
|---|---|
| LCP | 3.1秒 |
| Speed Index | 44.7秒 |
| ページ総重量 | 32.4MB |
LCPだけを見ると「速度は問題なし」と判断してしまいます。上記の表のとおり、遅さはSpeed Indexにだけ出ていました。
内訳を見ると、Webフォントの自前配信が大半を占めていました。日本語フォント Zen Kaku Gothic New を Regular / Medium / Bold / Black の4ウェイト分、サブセット化せずにそのまま置いていた状態です。
Regular 3,204 KB
Medium 3,104 KB
Bold 3,080 KB
Black 3,013 KB
-----------------
合計 12,401 KB(約12.1MB)→ 総重量32.4MBの37%
LCPが良いのに遅く感じる理由
CSSには以下の指定が入っていました。
font-display: swap;
swap が効いているので、本文はまず代替フォントで表示されます。そのため LCP は早く確定します。ただし3MB前後のフォントファイルが1本届くたびに文字が差し替わるので、画面が落ち着くまで44.7秒かかります。
「LCPは3.1秒なのに、画面が落ち着くまで44.7秒」というねじれは、先方に説明するときにいちばん伝わった言い方でした。
手順2: サブセット化後のサイズを実測で出す
「サブセット化すれば軽くなります」と言うだけでは推測にすぎません。実際に使われている文字だけを読み込ませて、転送量を実測しました。
2-1. 実サイトの可視テキストからユニーク文字を取る
Playwright で実サイトを開いて inner_text("body") で可視テキストを取り、重複を除いた文字の集合を作ります。以下のコードは要点だけを抜き出したものです。
text = page.inner_text("body")
chars = sorted(set(text))
今回のユニーク文字は310字でした。
2-2. 全文字を並べたローカルHTMLで Google Fonts を読ませる
2-1で得た310字をすべて並べたローカルHTMLを作り、Google Fonts の Zen Kaku Gothic New(4ウェイト)のCSSを読み込ませます。全ウェイトで全文字を表示させる状態が上限値になります。
2-3. fonts.gstatic.com のレスポンスを合計する
ページを開く前にレスポンスのハンドラを登録して、fonts.gstatic.com から返ってきたファイルのサイズを集めます。以下のコードも要点だけを抜き出したものです。
sizes = []
def on_response(r):
if "fonts.gstatic.com" in r.url:
sizes.append(len(r.body()))
page.on("response", on_response)
2-1から2-3までのスクリプトは全体で30行ほどです。
結果
ファイル数: 121
合計: 1,520 KB
全文字を4ウェイトすべてで表示しても1.5MBで収まります。12.1MBと比べると約1/8です。
手元で見た限り、Google Fonts 側は unicode-range で484に分割されていて、実際に使う範囲のファイルしかダウンロードされませんでした。1.5MBは「最大でもこの程度」という上限値なので、実際のページ表示ではさらに少なくなる見込みです。
起きたこと/落とし穴
静的HTMLや自動ツールの出力をそのまま書いていたら、信用を失う指摘が3つありました。PSIの取得でもつまずきました。
誤検出1: mixed content と判定された外部ドメイン
capture の mixed_content_hosts に外部ドメインが出ました。中身を確かめると、実体は以下のような普通の外部リンクでした。
<a href="https://...">
描画後のネットワークを見ると、http:// へのリクエストは0件でした。mixed content ではないので、診断には書いていません。
誤検出2: 「H1が0個」はトップページだけだった
トップページにはH1がありませんでした。一方、下層10ページを確かめると9ページにはH1がありました。「全ページH1なし」と書いていたら事実と違う指摘になっていました。
あわせて alt 属性も確かめました。
-
altの欠落: 0枚 -
alt="": 16枚(装飾用途なので正しい)
Lighthouse の a11y スコア100とも食い違いはありません。
誤検出3: ソフト404と決めつけない
存在しないURLを5パターン試すと、5本とも 404 + noindex を返しました。404の扱いは正しく動いています。
一方で、実在はするが中身が空のページが1本見つかりました。マガジンのカテゴリが /about/ 配下と衝突して、「記事はありません」と表示されるページです。
書き方を比べると以下のようになります。
| 書き方 | 判定 |
|---|---|
| 404の処理が壊れている | 誤り |
| 中身が空のページが1本ある | 正確 |
PSI(CrUX実データ)が取れなかった
- APIキーなしの
pagespeedonlineは、その日のうちに quota を使い切りました - よく使われる迂回路として、
pagespeed.web.devを Playwright で開いて結果をポーリングする方法も試しました。URLは正しく入っていましたが、15分待ってもスピナーが回ったまま完了しませんでした(2回試行)
対象は32MB・134リクエストのページです。手元で試した限りでは、ここまで重いサイトはPSI側の計測が最後まで終わらないことがあり、迂回路が万能ではないと分かりました。
対処
実データが無いときの数字の書き方
CrUXが取れないときは、「実際の閲覧者にとって遅い」とは書けません。書けるのは計測条件つきの数字までです。
Lighthouse の低速環境(CPU4倍遅・下り1,474Kbps・キャッシュなし)で Speed Index 44.7秒
計測条件を一緒に書いておけば、CrUXが無くても第三者が検算できる数字になります。
さらに、バイト数(総重量32.4MB・フォント12.1MB)はCrUXの有無に関係なく確定している事実です。提案の軸はバイト数のほうに置きました。そうすれば「データが取れなかったので提案が弱い」という状態を避けられます。
恒久的な対策としては、無料で取れる PSI のAPIキーを取得しておきます。
改善提案は2案を並べる
「Google Fonts に切り替えましょう」の1案だけを出すと、閲覧者のIPアドレスがGoogleに渡ることを理由に、情シスの段階で止まりがちです。日本語Webフォントの自前配信は、自社で管理できることやオフラインでも安全なことを理由に選ばれるので、その理由を残す案も一緒に出します。
| 案 | 内容 | 効果 |
|---|---|---|
| ① | Google Fonts に切り替える | unicode-range 分割で必要な範囲だけ配信 |
| ② | 自前配信のまま unicode-range で分割する | ①とほぼ同じ |
上記の表のとおり、効果はどちらもほぼ同じです。先方の社内事情に合わせて選んでもらえます。
まとめ
- LCPが良くても、Speed Indexとページ総重量を必ず並べて見る。
font-display: swapがあると、フォントの重さはLCPに出ない - サブセット化の効果は推測で言わない。可視テキストのユニーク文字を Google Fonts に読ませ、
fonts.gstatic.comのレスポンスを合計すれば上限値が出る(今回は121ファイル・1,520KB) - 自動ツールの検出結果は、描画後のDOMとネットワークで確かめてから書く(mixed content・H1・alt・404)
- CrUXが取れないときは計測条件を併記し、提案の軸はバイト数のような確定した事実に置く
- 改善案は Google Fonts と自前配信の分割の2案を並べ、情シスで止まらないようにする