AstroでPageSpeedモバイル84→100点満点!FCP3.4秒を1.0秒にした4つの高速化術
作成日:

AstroでPageSpeedモバイル84→100点満点!FCP3.4秒を1.0秒にした4つの高速化術


🔧 論旨

超高速な静的サイト構築フレームワーク「Astro」でブログを作ったのに、PageSpeed Insights で計測するとデスクトップは 97 点と速い一方、肝心の 「携帯電話(モバイル)」だけ 84 点(オレンジ表示)で頭打ちになる ――そんな壁にぶつかったことはないでしょうか。当サイト(450 記事超)のトップページもまさにその状態で、最初の 3.4 秒間は画面に文字が 1 文字も出ない現象が起きていました。

結論からお伝えすると、モバイル 84 点(FCP・LCP 3.4 秒)の原因はデザインや記事数ではなく、「① Web フォントの描画待ちロック(FOIT)と無条件 preload」「② 圧縮後わずか 2.8KB の外部 CSS 通信往復」「③ 初期表示カードの透明アニメーション」「④ 放置された旧 GTM と廃止済みアナリティクスの空打ち」 の 4 点です。本記事では、見た目を 1 ピクセルも変えずにモバイル全 5 部門で 100 点満点(初回描画 1.0 秒) を達成した実測データと全設定コードを公開します。

🔧 合わせて読みたい深まる関連情報
🔧 対象となる方
  • Astro で静的サイトを作ったものの、PageSpeed Insights の 「携帯電話(モバイル)」スコアが 80 点台で伸び悩んでいる 個人開発者・ブロガーの方

  • 「効率的なキャッシュ保存期間(16 KiB)」「使用していない JavaScript(68 KiB)」「レンダリングをブロックしているリクエスト」の 赤三角(▲)警告の正体を根絶したい 方

  • Google AdSense や Search Console への悪影響ゼロ を確認したうえで、不要な計測タグや CSS・フォント設定を整理したい方

実測比較:モバイル84点から全5部門100点への変化

まずは論より証拠として、当サイトのトップページ(https://lab.chihouzakki.com/)における改修前後の PageSpeed Insights 実測レポート(携帯電話:Moto G Power エミュレーション + 低速 4G スロットリング)を並べて比較します。

改修前のPageSpeed Insights携帯電話スコア(パフォーマンス84点)

▲ 改修前:モバイルだけ 84 点(オレンジ)

改修後のPageSpeed Insights携帯電話スコア(全5部門100点満点)

▲ 改修後:モバイルでも全 5 部門 100 点満点!

さらに決定的な違いが現れたのが、レポート 2 ページ目の Core Web Vitals 指標と連続スクリーンショット(フィルムストリップ) です。

改修前のモバイル描画タイムライン(前半3コマが真っ白でFCP・LCPともに3.4秒)

▲ 改修前:前半 3 コマが真っ白で 3.4 秒目に突然出現

改修後のモバイル描画タイムライン(1コマ目から完成画面が表示されFCP・LCPともに1.0秒)

▲ 改修後:左端の 1 コマ目から完成画面が即時表示!

計測項目(携帯電話・低速 4G)改修前改修後改善幅・結果
パフォーマンス総合84100+16 点(完全満点)
First Contentful Paint (FCP)3.4 秒1.0 秒2.4 秒短縮
Largest Contentful Paint (LCP)3.4 秒1.0 秒2.4 秒短縮
Speed Index3.5 秒1.0 秒2.5 秒短縮
Total Blocking Time (TBT)0 ミリ秒0 ミリ秒ブロック時間ゼロ維持
Cumulative Layout Shift (CLS)00.001画面揺れ実質ゼロ維持
補助 / おすすめ / SEO / AI適合100 / 100 / 100 / 3/3100 / 100 / 100 / 3/3全項目パーフェクト
質問するおじさん

ちょっと待ってくれ!改修前の数字を見ると、JavaScript の詰まりを表す TBT は最初から「0 ミリ秒」じゃし、画面のズレ(CLS)も「0」じゃったんじゃろ?それなのになんでスマホだけ 3.4 秒も真っ白な画面で待たされとったんじゃ?

解説ナビゲーター

そこが一番のミステリーですよね!実は Astro 本体が遅かったのではなく、「初期テンプレート由来のフォント設定」と「小さな外部 CSS ファイル」、そして「古い計測タグ」 が低速 4G 回線で連鎖反応を起こしていたんです。


モバイル描画を3.4秒止めていた4つの真犯人

本番ページの通信ヘッダーと DOM、そして document.fonts の状態をブラウザ上で実測した結果、モバイルスコアを 16 点引き下げていた構造がはっきりと浮かび上がりました。

低速4G環境における初回描画(FCP / LCP)の構造比較
改修前後
改修前(FCP 3.4秒 / 84点)
直列通信と3秒の透明ロック
  • <head> 先頭の preload で英数字フォント .woff 2 本(計 46.6 KB)が回線帯域を最優先で占有

  • @font-face に font-display 指定がなく、フォント到着まで 最大 3 秒間テキストを透明化(FOIT)

  • 圧縮後わずか 2.8 KB の外部 CSS 取得でさらに 1 往復待ち、旧 GTM が廃止済み analytics.js を空打ち実行

改修後(FCP 1.0秒 / 100点)
HTML単一往復で即座に全文描画
  • スタイルシートを HTML 内へインライン統合し、HTML 1 ファイル(Brotli 圧縮後 21.3 KB)だけで即描画

  • font-display: swap 指定と preload 解除により、0 ミリ秒待ちでシステムフォントから即時表示

  • 死んでいた旧計測タグと初期カードの透明アニメーションを撤去し、外部ブロッキング通信を完全ゼロ化

それぞれの真犯人がどのように足を引っ張っていたのか、順番に解剖します。

真犯人①:font-display未指定による3秒ロック

最大の犯人は、Astro の公式ブログテンプレート(Bear Blog 系)に最初から入っている英数字用 Web フォント Atkinson の読み込み設定でした。

改修前の CSS(global.css)とヘッド定義(BaseHead.astro)を確認すると、次の 2 つの問題が同時に起きていました。

  1. @font-face に font-display が書かれていなかった : CSS の @font-face で font-display を省略すると初期値の auto となり、Chromium(Lighthouse)を含む大半のブラウザでは block 相当(最大約 3 秒間、フォントが届くまで文字を透明にして隠す FOIT:Flash of Invisible Text) として動作します。

  2. 合計 46.6 KB の .woff 2 本を最優先 preload していた : atkinson-regular.woff(22.8 KB)と atkinson-bold.woff(23.8 KB)に <link rel="preload" as="font"> が付いていたため、スマホの低速 4G 回線(約 1.6 Mbps)では、肝心の CSS(2.8 KB)よりも先に 46.6 KB のフォント転送が割り込んでいました。

その結果、2 本のフォントがダウンロードし終わる 3.4 秒目まで、見出しも本文もすべて透明のままロックされ、3.4 秒目に一気に全文字が出現する(FCP = LCP = 3.4 秒) という現象が起きていたのです。

真犯人②:圧縮後2.8KBの外部CSS往復と初期アニメ

2 つ目の要因は、PageSpeed Insights で 「レンダリングをブロックしているリクエスト」 として指摘されていた外部 CSS ファイル(/_astro/index.*.css)と、初期表示カードに付与されていたフェードイン演出です。

Astro の build.inlineStylesheets 設定は初期値が 'auto' となっており、非圧縮サイズで一定の閾値(約 4 KB)を超えるスタイルシートは外部 .css ファイルとして切り出されます。トップページの CSS は展開後こそ約 13.3 KB ありますが、Brotli 圧縮後の実転送サイズはわずか 2.8 KB しかありません。

たった 2.8 KB のためにモバイル回線で「HTML 受信 → CSS リクエスト → CSS 受信」という追加の往復遅延(RTT)を払うのは大きな損です。さらに、タブ切り替え時の演出用クラス .fade-in(opacity: 0 から 0.4 秒かけて不透明にするアニメーション)がファーストビュー内の静的な比較カードにも最初から付いていたため、描画完了の確定を余計に遅らせていました。

真犯人③:旧GTMと廃止済みUAアナリティクスの空打ち

そして今回、実測調査をして最も驚いたのが 「効率的なキャッシュ保存期間を使用する(推定削減 16 KiB)」 と 「使用していない JavaScript の削減(推定削減 68 KiB)」 という 2 つの赤三角(▲)警告の正体でした。

当サイトはすでにエックスサーバーの .htaccess(mod_expires)によって、自社の CSS・フォント・WebP 画像すべてに max-age=31536000(1 年間キャッシュ)が効いています。「それなのに、いったい何がキャッシュ切れを起こしているのか?」とネットワーク通信を実測したところ、意外な犯人が見つかりました。

🔧 🔍 ネットワーク実測で判明した警告の真犯人
  • BaseHead.astro に残っていた古い Google Tag Manager(GTM-XXXX) が、gtm.js(未使用分 約 68 KiB)を読み込んでいた。

  • さらにその GTM コンテナの内部から、Google がすでに廃止・受付終了した旧世代の Universal Analytics(UA-XXXX / google-analytics.com/analytics.js) が自動発火していた。

  • この analytics.js は Google 側のサーバー仕様で Cache-Control: max-age=7200(わずか 2 時間) に固定されているため、自社サーバーをいくらチューニングしても毎回「16 KiB のキャッシュ期間不足」として減点・警告されていた。

過去の PC 移行・ブランチ統合の際に古い GTM タグへ先祖返りしており、どこにも保存されない廃止済みアナリティクスへ空打ち送信するために、毎ページ約 85 KB 超のスクリプトを動かしていた というのが真相でした(デスクトップ版に出ていた「強制リフロー」警告も、このスクリプトが画面サイズを計測する処理が主因でした)。


デザイン無改変で100点を出す4ステップ実装コード

原因が 100% 特定できれば、対策は驚くほどシンプルです。ページの見た目や機能には一切手を加えず、以下の 4 ステップを順番に適用しました。

モバイル100点満点を達成する4ステップ改修フロー
STEP 1
フォント即時表示

@font-face に font-display: swap を追加し、無条件 preload を削除

STEP 2
CSSインライン化

astro.config.mjs で inlineStylesheets: 'always' を指定

STEP 3
初期アニメ整理

初回表示カードの opacity: 0 と同期リフロー(offsetWidth)を解消

STEP 4
旧計測タグ撤去

旧 GTM と noscript を削除し、運営ポリシーの外部送信表記も整合

フォントへのswap指定と不要なpreloadの削除

まず src/styles/global.css の @font-face に font-display: swap; を明示します。あわせて src/components/BaseHead.astro にあった 2 行の <link rel="preload" href="/fonts/atkinson-*.woff" as="font" ... /> を削除しました。

/* src/styles/global.css */
@font-face {
  font-family: "Atkinson";
  src: url("/fonts/atkinson-regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Atkinson";
  src: url("/fonts/atkinson-bold.woff") format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
質問するおばさん

font-display: swap にすると、後からフォントが切り替わった瞬間に文字幅が変わって、画面がガタッと揺れる(CLS が悪化する)ことはないのかしら?

解説ナビゲーター

鋭い質問ですね!実は 日本語主体のサイトではその心配がほぼない んです。Atkinson は英数字専用フォントなので、日本語(ひらがな・カタカナ・漢字)は最初から最後まで OS 標準のゴシック体で描画されます。そのため行の高さや折り返し位置がほとんど動かず、実測でも CLS は 0.001(合格基準 0.1 以下の 100 分の 1)にピタッと収まりました!

inlineStylesheetsと初期アニメの整理

次に astro.config.mjs の build オプションへ inlineStylesheets: 'always' を 1 行追加します。

// astro.config.mjs(該当箇所抜粋)
export default defineConfig({
  site: "https://lab.chihouzakki.com",
  build: {
    concurrency: 6,
    inlineStylesheets: "always", // 全スタイルをHTML内 <style> に埋め込み外部CSS往復をゼロ化
  },
});

これにより、トップページは HTML と CSS をすべて合わせても Brotli 圧縮後 21.3 KB の単一レスポンス に収まり、HTML が届いた瞬間にブラウザが 1 回の追加通信も待たずに画面を描き始められるようになりました。

あわせてトップページ(src/pages/index.astro)の初期表示カードから .fade-in クラスを外し、タブ切り替えクリック時にアニメーションを再発火させていた処理も、同期的な強制リフローを起こす void descBox.offsetWidth; から requestAnimationFrame を使う形へ書き換えています。

// タブクリック時のアニメーション再発火(強制リフローを起こさない書き方)
descBox.classList.remove("fade-in");
descBox.innerHTML = `<span class="persona-desc-title">${data.title}</span><p class="persona-desc-text">${data.text}</p>`;
requestAnimationFrame(() => {
  descBox.classList.add("fade-in");
});

解析タグを外してもAdSenseとGSCは無傷な理由

最後に、古い GTM スクリプト(および記事レイアウト側の <noscript> 用 GTM iframe)を撤去する際、多くの方が心配される 「Google AdSense や Search Console まで止まってしまわないか?」 という疑問について、技術的な切り分けを整理しておきます。

🔧 🛡️ GTM・アナリティクスを撤去しても影響ゼロな理由と代替案
  1. Google AdSense は独立稼働のため影響ゼロ : AdSense の広告配信と収益計測は、専用スクリプト(adsbygoogle.js)と広告枠(<ins class="adsbygoogle">)、およびルート直下の ads.txt で完全に独立して動作します。GTM や Google Analytics の有無によって収益や広告表示が変わることはありません。

  2. Google Search Console も影響ゼロ : 検索クリック数・表示回数・検索クエリ・順位は Google 検索エンジン側で直接記録されるため、サイト内の解析 JS は不要です。ただし サイトの所有権確認を「GTM タグ」だけで行っている場合のみ注意が必要 なため、事前に DNS レコード認証(sc-domain:)または <meta name="google-site-verification" ... /> タグが入っていることを確認してから外してください。

  3. プライバシーポリシー(外部送信規律)の実態整合 : 解析タグを完全に撤去した場合は、運営ポリシーページにある「Google アナリティクスへの外部送信表記」も忘れずに削除しておくと、実態と規約が 100% 一致したクリーンなサイトになります(逆に GA4 計測をどうしても使いたい場合は、古い GTM をやめて現行の G- 測定 ID のみに絞り、window の load 後や初回スクロール後に遅延読み込みさせるのが現実的です)。

なお、改修後の PageSpeed Insights レポート詳細を開くと「ネットワークの依存関係ツリー(クリティカル パスの最大待ち時間: 375 ms)」に .woff フォント 2 本が表示される場合がありますが、右上に スコアなし と表示されている通り減点対象ではありません。font-display: swap によって初回描画(1.0 秒)をブロックせず裏側で読み込まれている健全な記録ですので、そのまま放置して問題ありません。

ノルウェージャンフォレストキャット

死んどった古い計測タグをお掃除して CSS とフォントの順番を整えただけで、見た目そのままなのに 3.4 秒が 1.0 秒になって 100 点満点が出たんでちね!大掃除は大事でち!


まとめ:見た目を変えずにスマホ表示1.0秒を実現する

Astro はもともとクライアント JavaScript を極限まで削ぎ落とせる優秀なフレームワークですが、だからこそ 「フォントの font-display 漏れ」「数 KB の外部 CSS 往復」「昔入れたままの古い計測タグ」 といった小さな栓が 1 つ残っているだけで、モバイル回線では 2〜3 秒もの待ち時間になって現れます。

記事のデザインや情報量を削る前に、まずは 「ブラウザが最初の 1 文字を描くまでに何を待たされているか」 を実測して栓を抜くだけで、スマホでも 1.0 秒・100 点満点の快適さを手に入れられます。
🔧 ✅ 本チューニングで幸せになる人・注意点
  • 導入して幸せになる人 : Astro サイトのデザインや記事構成を一切崩さず、モバイルの初回表示体感速度と Core Web Vitals を最高水準まで引き上げたい方

  • 事前に確認すべき注意点①(所有権確認) : GTM を削除する場合は、必ず Search Console の所有権確認が DNS 認証または google-site-verification メタタグで行われていること(GTM 単独認証になっていないこと)を先に確認してください

  • 事前に確認すべき注意点②(複数レイアウト併用時の <style is:global> 干渉) : 動的ルート([...slug].astro 等)で複数のレイアウト(黒背景固定ページとライト/ダーク切替ページなど)を import して出し分けている場合、inlineStylesheets: 'always' にすると HTML 内 <style> の結合順序が変わり、一方の <style is:global> にある body 指定が他方のレイアウトへ後勝ちで漏出することがあります。各レイアウトの <body class="review-post-body"> のようにクラス詳細度と CSS 変数を明示して保護しておくと万全です

ご自身の Astro サイトでも同じボトルネックが潜んでいないか診断・改善したい場合は、以下のプロンプトをコピーして最上位モデルの AI エディタへ渡してみてください。

🔧 🤖 自分のAstro環境をAIに診断・高速化させるコピペ用プロンプト

※軽量モデル(Flash 等)では Search Console の所有権認証方式やページ別の CSS 影響範囲の確認を読み飛ばす恐れがあるため、必ず 『Gemini 4 Argon』(または同等以上の最上位推論モデル)を選択して実行してください。

以下の記事の実測知見と手順を参照し、私の Astro プロジェクトのモバイル表示速度(FCP / LCP)ボトルネックを調査して改善計画を提案してください。
参照URL: https://lab.chihouzakki.com/blog/review/astro-pagespeed-mobile-100-foit-inline-css-fix/

【作業ルール(厳守)】
1. いきなりファイルを書き換えず、まず私のプロジェクトの astro.config.*、グローバルCSS(@font-face)、共通ヘッドコンポーネント(preload・GTM・GA4・AdSense・google-site-verification)、ファーストビューの初期アニメーション(opacity: 0 等)、および動的ルートで複数レイアウトを併用している場合の <style is:global> 干渉リスクをすべて調査してください。
2. 計測タグ(GTM / GA4)が存在する場合は、無断で削除せず「①現行GA4の遅延読み込み化」か「②完全撤去(Search Console認証タグ・AdSenseへの影響確認+プライバシーポリシー整合込み)」の選択肢を提示して私の判断を仰いでください。
3. デザインやレイアウトを1ピクセルも崩さずに FCP / LCP を短縮できる具体的な変更ファイル一覧を提示し、私の合意を得てから実装してください。

スマホで開いた瞬間にパッと記事が立ち上がる気持ちよさを整えて、読者にも検索エンジンにも喜ばれるサイトを育てていきましょう!

すっきり片付いた部屋で安心してお昼寝するノルウェージャンフォレストキャット

▲ 無駄な通信待ちがゼロになれば、待つストレスもなくなってぐっすり眠れます