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 秒) を達成した実測データと全設定コードを公開します。
-
【Xserver×Astro移行の罠】なぜ404ではなく500無限ループになるのか?親WordPress『.htaccess継承・日本語URL後方参照バグ・XPageSpeed』完全解決録 : 本記事の前提となる、Xserver 側での WebP・CSS・フォント 1 年キャッシュ設定(
mod_expires)と重複 URL 対策の実装録です。 -
Astro×Google AdSense手動広告の自動挿入ガイド|rehypeとllms.txt完全分離 : トップページや思想系記事には広告スクリプトを一切読み込ませず、レビュー記事のみ自動で 2 枠配置する分離設計を解説しています。
-
Search Console「未登録」が多い時の見方|送信済み絞り込みとAstroサイトマップ改善 : 表示速度と並んで検索流入の土台となる、Googlebot の巡回効率とインデックス登録率の引き上げ手順です。
-
Search Consoleの更新が1週間止まる原因と「GoogleOther(29%)」の正体 : Google Search Consoleのインデックス登録更新が1週間止まって見える時の3段階切り分け手順と、クロール統計でPC・スマホを抜いて1位になった「その他のエージェント(GoogleOther)」の正体を解説します。
-
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 スロットリング)を並べて比較します。

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

▲ 改修後:モバイルでも全 5 部門 100 点満点!
さらに決定的な違いが現れたのが、レポート 2 ページ目の Core Web Vitals 指標と連続スクリーンショット(フィルムストリップ) です。

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

▲ 改修後:左端の 1 コマ目から完成画面が即時表示!
| 計測項目(携帯電話・低速 4G) | 改修前 | 改修後 | 改善幅・結果 |
|---|---|---|---|
| パフォーマンス総合 | 84 | 100 | +16 点(完全満点) |
| First Contentful Paint (FCP) | 3.4 秒 | 1.0 秒 | 2.4 秒短縮 |
| Largest Contentful Paint (LCP) | 3.4 秒 | 1.0 秒 | 2.4 秒短縮 |
| Speed Index | 3.5 秒 | 1.0 秒 | 2.5 秒短縮 |
| Total Blocking Time (TBT) | 0 ミリ秒 | 0 ミリ秒 | ブロック時間ゼロ維持 |
| Cumulative Layout Shift (CLS) | 0 | 0.001 | 画面揺れ実質ゼロ維持 |
| 補助 / おすすめ / SEO / AI適合 | 100 / 100 / 100 / 3/3 | 100 / 100 / 100 / 3/3 | 全項目パーフェクト |
ちょっと待ってくれ!改修前の数字を見ると、JavaScript の詰まりを表す TBT は最初から「0 ミリ秒」じゃし、画面のズレ(CLS)も「0」じゃったんじゃろ?それなのになんでスマホだけ 3.4 秒も真っ白な画面で待たされとったんじゃ?
そこが一番のミステリーですよね!実は Astro 本体が遅かったのではなく、「初期テンプレート由来のフォント設定」と「小さな外部 CSS ファイル」、そして「古い計測タグ」 が低速 4G 回線で連鎖反応を起こしていたんです。
モバイル描画を3.4秒止めていた4つの真犯人
本番ページの通信ヘッダーと DOM、そして document.fonts の状態をブラウザ上で実測した結果、モバイルスコアを 16 点引き下げていた構造がはっきりと浮かび上がりました。
-
<head>先頭のpreloadで英数字フォント.woff2 本(計 46.6 KB)が回線帯域を最優先で占有 -
@font-faceにfont-display指定がなく、フォント到着まで 最大 3 秒間テキストを透明化(FOIT) -
圧縮後わずか 2.8 KB の外部 CSS 取得でさらに 1 往復待ち、旧 GTM が廃止済み
analytics.jsを空打ち実行
-
スタイルシートを 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 つの問題が同時に起きていました。
-
@font-faceにfont-displayが書かれていなかった : CSS の@font-faceでfont-displayを省略すると初期値のautoとなり、Chromium(Lighthouse)を含む大半のブラウザではblock相当(最大約 3 秒間、フォントが届くまで文字を透明にして隠す FOIT:Flash of Invisible Text) として動作します。 -
合計 46.6 KB の
.woff2 本を最優先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 ステップを順番に適用しました。
@font-face に font-display: swap を追加し、無条件 preload を削除
astro.config.mjs で inlineStylesheets: 'always' を指定
初回表示カードの opacity: 0 と同期リフロー(offsetWidth)を解消
旧 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 まで止まってしまわないか?」 という疑問について、技術的な切り分けを整理しておきます。
-
Google AdSense は独立稼働のため影響ゼロ : AdSense の広告配信と収益計測は、専用スクリプト(
adsbygoogle.js)と広告枠(<ins class="adsbygoogle">)、およびルート直下のads.txtで完全に独立して動作します。GTM や Google Analytics の有無によって収益や広告表示が変わることはありません。 -
Google Search Console も影響ゼロ : 検索クリック数・表示回数・検索クエリ・順位は Google 検索エンジン側で直接記録されるため、サイト内の解析 JS は不要です。ただし サイトの所有権確認を「GTM タグ」だけで行っている場合のみ注意が必要 なため、事前に DNS レコード認証(
sc-domain:)または<meta name="google-site-verification" ... />タグが入っていることを確認してから外してください。 -
プライバシーポリシー(外部送信規律)の実態整合 : 解析タグを完全に撤去した場合は、運営ポリシーページにある「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 秒もの待ち時間になって現れます。
-
導入して幸せになる人 : 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 エディタへ渡してみてください。
※軽量モデル(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 を短縮できる具体的な変更ファイル一覧を提示し、私の合意を得てから実装してください。 スマホで開いた瞬間にパッと記事が立ち上がる気持ちよさを整えて、読者にも検索エンジンにも喜ばれるサイトを育てていきましょう!

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