NOTES

このサイトのモバイル表示を 59 点から 100 点にした記録

著者: 伊藤 峻公開: 更新:

原因は演出ではなく、読み込みの順番でした。日本語フォントの配信をやめ、英数フォントを自サイトから出し、地図と WebGL を最初の描画の後に回したところ、LCP は 9,972ms から約 980ms になりました。

結論

このサイト(mmstd.cc)は、2026 年 9 月の刷新直後、モバイルの Lighthouse が 59 点、LCP(最大の要素が描かれるまでの時間)が 9,972ms でした。原因は WebGL の背景や入場アニメーションではなく、読み込みの順番です。5 回の対処で、トップは 89〜100 点(速い回で LCP 約 980ms)、受け皿ページは安定して 100 点になりました。

時点 トップ Web の困りごと
刷新直後 59 点(LCP 9,972ms) 59 点(LCP 7,570ms)
対処後 89〜100 点(LCP 約 980ms〜) 100 点(LCP 約 980ms)

CLS(レイアウトのずれ)は全計測で 0 です。デスクトップは 100 点(LCP 287ms)でした。計測は Lighthouse 13.4.1、モバイル・4G 相当のシミュレーションです。

何が起きていたか

刷新したサイトには、素の WebGL で描く流体の背景と、スクロールに合わせた出現アニメーションがあります。最初はこれらを疑いました。しかし WebGL を持たない受け皿ページも同じ 59 点だったので、演出は無関係だと分かりました。

ネットワークの記録を見ると、Google Fonts の CSS(日本語フォント込みで 92KB)が描画をブロックし、続いて日本語フォント本体を 6 ファイル約 180KB 取得していました。4G 相当の回線では、この待ち時間がそのまま最初の描画の遅れになります。

対処 1: 日本語フォントを配信しない

iOS と macOS はヒラギノ、Android は Noto Sans CJK、Windows は游ゴシックかメイリオを標準で持っています。日本語フォントの配信をやめ、端末のフォントを使うようにしました。見出し用の英数フォント(Jura、約 24KB)だけを残しています。

ローカルの計測では mobile 100 点(LCP 1,599ms)になりました。ただし本番では、トップが 67〜100 点の間で揺れました。

対処 2: 英数フォントを自サイトから配信する

揺れの原因を追うと、見出しが LCP として記録されるのが、最初の描画から 0.4〜0.8 秒遅れていました。Google Fonts のリンクを外すと遅れが消えます。外部の CSS で @font-face を後から読み込むと、その CSS が届くまでブラウザは見出しを LCP の候補として確定しないようです。preload で非ブロッキングにしても、この待ちは消えませんでした。

Jura の woff2 を自サイトに置き、@font-face をブロッキングの CSS 側に書きました。これで最初のスタイル解決の時点でフォントが既知になります。LCP は最初の描画と一致し、ローカルでは 1,056〜1,060ms に安定しました。

対処 3: 最初の画面は CSS だけで出す

ヒーローの出現アニメーションは、JavaScript が class を付ける方式でした。JS を待つ間、見出しは opacity 0 のままです。最初の画面に入る要素は CSS のアニメーションだけで出すように変え、JS が失敗しても最初の画面が必ず表示されるようにしました。

WebGL の起動も、最初の描画が終わってから(読み込み完了後のアイドル時)に回しています。GPU のない計測環境では、シェーダーのコンパイルが 1 秒近く主スレッドを止めることがあるためです。

対処 4: 地図の埋め込みを後回しにする

本番でトップだけが揺れる理由を、外部リソースを 1 つずつブロックして切り分けました。Google マップの埋め込みをブロックした 2 回だけが、安定して最初の描画 200ms 以内でした。

地図の iframe には loading=“lazy” を付けていましたが、モバイルの低速回線では Chrome がかなり手前から先読みを始めます。実測では HTML の解析直後(131〜409ms)に地図の JavaScript 80KB 以上の取得と実行が始まり、本体の描画を 1〜2 秒押しのけていました。

地図の src を最初の HTML には書かず、枠だけを置いて、枠が画面に近づいたときに JavaScript で iframe を作る方式に変えました。スクロールしない方のために、読み込み完了から 2 秒後にも作ります。JavaScript が無効な環境には noscript で従来どおりの iframe を出します。枠には min-height を先に与え、差し込み時のずれを防ぎました。初期表示時点のリクエストは 28 件から 11 件になっています。

対処 5: WebGL の canvas を最初の HTML に置かない

それでも本番で「最初の描画が約 1.44 秒で止まる」回が残りました。canvas を初期 HTML から外し、読み込み完了後に JavaScript で生成する方式にしましたが、これは効きませんでした。

残った課題

トップだけ、半数ほどの計測で最初の描画が約 1.44 秒に張り付きます。トレースを取ると、その区間は主スレッドが完全に空で、HTML も CSS もレイアウトも 405ms までに終わっています。JavaScript を切って本番をレンダリングしても、ヘッダーから見出し、本文、ボタンまで全て表示されます。つまり描画すべき中身は最初から揃っており、ページ側の依存の問題ではありません。計測環境側の揺れの可能性が高いのですが、トップだけに偏る理由は説明できていません。PageSpeed Insights と実ユーザーの値で判断を続けます。

やらなかったこと

  • 画像の最適化: このサイトには画像がほとんどありません
  • JavaScript の削減: 元から 5KB 程度で、主スレッドを塞いでいませんでした
  • llms.txt の設置: 効果を示す根拠が見つかりませんでした

学んだこと

  • 最初の 1 秒を決めるのは、演出ではなく読み込みの順番です
  • 「非ブロッキング」にしても、LCP の判定に影響するものがあります。フォントの @font-face はその例です
  • loading=“lazy” は「後で読む」の約束ではありません
  • 計測は複数回行います。1 回の数値で判断すると、外れ値を原因だと思い込みます

計測の方法

npx lighthouse@13.4.1 <URL> --form-factor=mobile --screenEmulation.mobile --throttling-method=simulate

本番は毎回 3〜5 回計測して分布を見ました。ローカルでは再現しない揺れがあったため、切り分けは本番のオリジンに対して行っています。

  • Core Web Vitals
  • Astro
  • Cloudflare
  • 計測

← ノートの一覧へ