メインコンテンツへスキップ
← 記事一覧に戻る
·開発·10 min read

Next.js 16 App Router + MDX ブログを PSI API で全ページ計測したら mobile Lighthouse 全件赤だった (平均 57.7)

Next.jsLighthouseモバイル最適化パフォーマンスPSI API個人開発

結論

Next.js 16 App Router + MDX 構成でブログを 110 本書いた状態で、PageSpeed Insights API(mobile preset)で全ページを計測したら、perf >= 80 が 1 本もなかった

指標実測値
計測対象110 記事
perf >= 800 本
perf >= 700 本
平均スコア57.7
中央値59
最高スコア68
最低スコア28
perf < 5012 本
a11y95-97(問題なし)
SEO100(問題なし)

ローカルで desktop Lighthouse を叩いたときは 89-91 だったので、「本番は大丈夫だろう」と完全に油断していました。


この記事の前提

  • サイト構成: Next.js 16 App Router + MDX + Tailwind CSS + Vercel
  • 記事数: 110 本(技術系 + 個人開発マネタイズ系の混在)
  • 計測方法: PageSpeed Insights API を Node.js スクリプトで叩き、全 URL を直列処理
  • 計測プリセット: strategy=mobile(PSI API デフォルトの desktop とは別に指定)
  • 比較基準: ローカルで chrome-lighthouse を --preset=desktop で叩いた値(89-91)

「それ俺だ」と思う人向け

  • Next.js ブログの Lighthouse スコアをローカルでしか確認していない
  • Chrome DevTools の Lighthouse タブで計測して「90 台だし問題ない」と判断している
  • mobile ユーザーのことは意識しているつもりだが、実際に mobile preset で計測したことはない
  • Vercel の Analytics dashboard でパフォーマンスを確認しているが、具体的なスコアは把握していない

何が起きたか

masatoman.net の記事が 100 本を超えたタイミングで、full-site の技術的な状態を把握しようと PSI API での一括計測スクリプトを書きました。

// scripts/psi-audit.mjs
const PSI_API = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed'
const API_KEY = process.env.PSI_API_KEY

async function auditUrl(url) {
  const params = new URLSearchParams({
    url,
    strategy: 'mobile',
    key: API_KEY,
  })
  // category は複数回指定できないため URL を手動で組み立てる
  const fullUrl = `${PSI_API}?${params}&category=performance&category=accessibility&category=seo`
  const res = await fetch(fullUrl)
  const json = await res.json()
  const cats = json.lighthouseResult?.categories
  return {
    url,
    perf: Math.round((cats?.performance?.score ?? 0) * 100),
    a11y: Math.round((cats?.accessibility?.score ?? 0) * 100),
    seo:  Math.round((cats?.['seo']?.score ?? 0) * 100),
  }
}

// 全記事 URL を配列で渡して直列処理(API レート制限対策)
const urls = [/* 全 110 URL */]
const results = []
for (const url of urls) {
  await new Promise(r => setTimeout(r, 500)) // 0.5s 間隔
  results.push(await auditUrl(url))
}

import { writeFileSync } from 'fs'
writeFileSync('psi-results.json', JSON.stringify(results, null, 2))

計測結果を集計したら、以下が判明しました。

perf >= 80: 0 本 (0.0%)
perf >= 70: 0 本 (0.0%)
perf >= 60: 28 本 (25.5%)
perf >= 50: 98 本 (89.1%)
perf < 50: 12 本 (10.9%)

平均: 57.7
中央値: 59
最高: 68
最低: 28

a11y と SEO は全ページで高水準(95-97 / 100)だったので、問題は純粋に パフォーマンス、それも mobile に集中していることが確認できました。


原因の仮説

1. desktop と mobile の測定条件の違い

ローカルで計測していた --preset=desktop は、CPU と回線速度を高速デバイス想定でシミュレートします。

PSI API の strategy=mobile は、低速な mid-range Android(Moto G Power 相当)+ 4G 回線をシミュレートします。

条件desktop(ローカル)mobile PSI API
CPU スロットリング1x4x(4倍遅く)
回線速度無制限(ローカル)10 Mbps DL / 40ms RTT
デバイスメモリ実機制約あり

同じ HTML/JS でも、これだけ条件が違えばスコアが 1.5 倍以上乖離するのは自然です。

2. MDX コンテンツの JS バンドルへの影響

Next.js App Router + MDX 構成では、MDX ファイルを処理するための @next/mdx やカスタムコンポーネントが JS バンドルに含まれます。

110 本の記事にわたってカスタムコンポーネント(<NewsletterCTA /><ExperienceBox /> など)を使っていることで、初回ロード時の JS パース時間が mobile デバイスで無視できない水準になっている可能性があります。

3. 画像最適化の残課題

以前の記事で 11MB 画像の WebP 変換を実施しましたが、すべての記事で OGP 画像や埋め込み画像が最適化されているわけではありません。mobile では LCP(Largest Contentful Paint)の計測がよりシビアになります。

4. ローカルの loopback 接続効果

別の記事で触れたように、ローカル計測では loopback 接続のためネットワーク遅延がほぼゼロです。本番 URL で desktop 計測した場合は 90 台でした。mobile は本番 URL でも別の理由でスコアが下がります。


今の判断

この数字が「サービス致命的」かどうかは、Google の検索順位への影響実際のユーザー体験から判断する必要があります。

現時点では以下の優先度で対処を考えています。

優先度高:LCP 改善

  • OGP 画像の WebP 変換を全記事で統一
  • next/imagepriority prop を記事トップ画像に付与
  • 不要な自作コンポーネントの Server Component 化(クライアント JS を減らす)

優先度中:JS バンドルサイズ削減

  • @next/bundle-analyzer でバンドル内訳の可視化
  • MDX カスタムコンポーネントを dynamic import に切り替えて初回ロード対象から除外

優先度低:キャッシュ設定

  • Vercel の Cache-Control 設定が MDX ページに正しく適用されているか確認

具体的な改善作業は別タスクとして切り出しており、優先度の高い案件が終わったタイミングで着手します。まず実態を把握することを優先しました。


今日やること(3 つ)

  1. PSI API キーを取得して自分のサイトを 1 URL 計測してみる

    • Google Cloud Console で PageSpeed Insights API を有効化 → API キーを発行
    • curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://yourdomain.com&strategy=mobile&category=performance&key=YOUR_KEY" で確認
    • .categories.performance.score * 100 の値を確認する
  2. ローカル desktop と mobile PSI API のスコア差を比較する

    • ローカルの Lighthouse は DevTools → Lighthouse タブ → Desktop で計測
    • 差が 20 点以上あれば mobile 最適化が後回しになっているサイン
  3. perf スコアが最も低いページの LCP 要因を特定する

    • PSI API レスポンスの lighthouseResult.audits['largest-contentful-paint'] で LCP 要因を確認
    • 画像なら next/image + priority={true} で対応できる可能性が高い

で、どう稼ぐ?

「mobile Lighthouse が低いと稼げない」というのは、SEO 流入が収益源になる場合に限った話です。

現時点での masatoman.net は SEO 流入がほぼゼロ(計測中)で、主な流入はソーシャルと直接流入です。ただし、個人開発ブログで長期的に収益を作るなら SEO 流入は無視できません。Google の Core Web Vitals は検索ランキングの信号の 1 つであり、mobile performance が低いサイトは同じコンテンツ品質でも上位表示されにくい構造があります。

perf < 50 が 12 本存在しており、これらが仮に検索流入ターゲット記事だとすれば、本来取れるはずのオーガニック流入を取りこぼしている可能性があります。

「すべてを改善する」のではなく、収益に直結しているページを優先改善するというのが現実的な判断です。SEO ターゲット記事を 10 本に絞り、その 10 本だけ mobile perf 80 台を目指す、という戦略が私の現状には合っています。

Claude Crew Lab では、こうした「どこから手をつけるか」の判断フローも共有しています。

masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通

masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。


Next Step

次に読むならこの導線です

すべての記事を見る
有料で次へ進む¥1,000

【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード

Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。

次の実験記録も追う

Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。

この記事が役に立ったらシェア