Next.js 16 App Router + MDX ブログを PSI API で全ページ計測したら mobile Lighthouse 全件赤だった (平均 57.7)
結論
Next.js 16 App Router + MDX 構成でブログを 110 本書いた状態で、PageSpeed Insights API(mobile preset)で全ページを計測したら、perf >= 80 が 1 本もなかった。
| 指標 | 実測値 |
|---|---|
| 計測対象 | 110 記事 |
| perf >= 80 | 0 本 |
| perf >= 70 | 0 本 |
| 平均スコア | 57.7 |
| 中央値 | 59 |
| 最高スコア | 68 |
| 最低スコア | 28 |
| perf < 50 | 12 本 |
| a11y | 95-97(問題なし) |
| SEO | 100(問題なし) |
ローカルで 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 スロットリング | 1x | 4x(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/imageのpriorityprop を記事トップ画像に付与- 不要な自作コンポーネントの Server Component 化(クライアント JS を減らす)
優先度中:JS バンドルサイズ削減
@next/bundle-analyzerでバンドル内訳の可視化- MDX カスタムコンポーネントを
dynamic importに切り替えて初回ロード対象から除外
優先度低:キャッシュ設定
- Vercel の Cache-Control 設定が MDX ページに正しく適用されているか確認
具体的な改善作業は別タスクとして切り出しており、優先度の高い案件が終わったタイミングで着手します。まず実態を把握することを優先しました。
今日やること(3 つ)
-
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の値を確認する
-
ローカル desktop と mobile PSI API のスコア差を比較する
- ローカルの Lighthouse は DevTools → Lighthouse タブ → Desktop で計測
- 差が 20 点以上あれば mobile 最適化が後回しになっているサイン
-
perf スコアが最も低いページの LCP 要因を特定する
- PSI API レスポンスの
lighthouseResult.audits['largest-contentful-paint']で LCP 要因を確認 - 画像なら
next/image+priority={true}で対応できる可能性が高い
- PSI API レスポンスの
で、どう稼ぐ?
「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
次に読むならこの導線です
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第1回】Claude Code Skills 入門 — 自作スキルで開発効率を2倍にする実装ガイド
Claude Code の Skills 機能を自作する手順を、masatoman.net 周辺の自動化を Claude Code で回している立場で実コード付きで解説。1 スキル 15 分の投資で月 10 時間の作業を削減する実装ガイドです。
【第2回】Claude Code MCP サーバー構築ガイド — ゼロから作る実用MCPの全コード
Claude Code に外部システムを接続する MCP サーバーをゼロから自作する手順を、実コード付きで解説。月500万トークン運用の筆者が「実用ライン」の MCP 設計を全公開します。
次の実験記録も追う
Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
この記事が役に立ったらシェア