スマホ SEO対策を比較|違い・選び方・用途別の最適解
書き出し
モバイルが全トラフィックの60%超を占め、¹ Googleはモバイルファーストインデックスへ完全移行、² さらに2024年3月にINPがCore Web Vitalsへ正式格上げされた。³ 実務ではLCP/INP/CLSの数値が順位と収益に直結し、0.1秒の改善がCVRを有意に押し上げるケースも報告されるが、効果はサイトや導線に依存する。だが「何を先に実装するか」は難題だ。レンダリング方式、画像最適化、キャッシュ、計測運用のどれもがスマホ特性に影響する。本稿は技術選定の比較軸を明確化し、コード・手順・ベンチマークで用途別の最適解まで落とし込む。
スマホSEOの比較軸と技術仕様
スマホSEOは「表示品質」と「クロール効率」の両輪で最適化する。前者はCore Web Vitalsと可用性、後者はURL設計とリソース最小化が要。以下は最小限の技術仕様だ。
| 項目 | 推奨/閾値 | 技術選択/設定 | 目的 |
|---|---|---|---|
| レンダリング | SSR/ISR中心、CSR最小化 | Next.js/RSC、Edge SSR | 初期描画とLCP短縮 |
| レイアウト | RWD(単一URL) ¹⁰ | media/contain、m-dot非推奨 ¹⁰ | 重複回避・クロール効率 |
| LCP | ≤ 2.5s (p75, モバイル) ⁴ | 画像最適化・preload・CDN | 主画像の高速描画 |
| INP | ≤ 200ms ⁸ | 優先度キュー・アイランド化 | タップ応答性 |
| CLS | < 0.1 ⁵ | 画像寸法固定・font-display | レイアウト安定 |
| TTFB | < 800ms(目安) | Edge/HTTP/2+/早期ヒント | サーバ応答短縮 |
| 画像 | AVIF/WebP、自動変換 ⁶ ¹¹ | next/image or CDN変換 ⁶ | 転送量削減 |
| JSバンドル | < 200KB gzip | dynamic import、RSC | 実行遅延抑制 |
| キャッシュ | SW + CDN | Stale-While-Revalidate | 再訪高速化 |
| 構造化 | JSON-LD(Product/Article等) | Headに注入 | SERP機能拡張 |
| 計測 | RUM + 合成(CrUX/BigQuery, LHCI) ⁹ | CrUX/BigQuery, LHCI | 継続改善 |
選定原則:単一URLのRWDを土台に、Edge/ISRで初期表示を最適化し、画像・JS最小化でCWVを満たす。計測はRUMで現実の端末・回線を追跡し、Lighthouse CIで回帰検出を自動化する。⁹
実装手順とコード
実装は「描画パスの短縮→転送削減→計測運用」の順で進める。
- レンダリング方針をSSR/ISRに統一し、CSRは対話必須領域に限定
- RWDを徹底(単一URL、viewport適正化、m-dot統合) ¹⁰
- 画像最適化(AVIF/WebP、正しいsizes、fetchpriority) ⁶ ¹¹
- JS分割(dynamic import、RSC/島構成)、優先度制御
- キャッシュ(CDN + Service Worker)、HTTP/2ヒント
- 構造化データとメタ(canonical、robots、lang)
- 計測基盤(RUM + Lighthouse CI + CrUX) ⁹
- 監視/アラート(しきい値逸脱でFail)
コード例1: Next.jsで画像最適化とLCP短縮
// app/(site)/page.tsx
import Image from 'next/image';
import { headers } from 'next/headers';
export default function Home() {
const h = headers();
const ua = h.get('user-agent') || '';
const isLowEnd = /Android\s(7|8|9)|iPhone\s6|Moto|Nokia/i.test(ua);
const sizes = isLowEnd ? '(max-width:600px) 90vw, 600px' : '(max-width:1200px) 50vw, 600px';
return (
<main>
<h1>Landing</h1>
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={630}
priority
fetchPriority="high"
sizes={sizes}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
</main>
);
}
ポイント:priority + fetchPriorityでLCP画像を最優先化。sizesで過剰転送を抑制。寸法指定でCLSを回避。⁴ ⁵ ⁶
コード例2: Expressで圧縮・キャッシュ・クリティカルCSS配信
// server/index.mjs (type: module)
import express from 'express';
import compression from 'compression';
import { readFile } from 'node:fs/promises';
const app = express();
app.use(compression());
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'public, max-age=300, stale-while-revalidate=86400');
next();
});
app.get('/critical.css', async (req, res) => {
try {
const css = await readFile('./dist/critical.css', 'utf8');
res.type('text/css').send(css);
} catch (e) {
console.error('CSS load error', e);
res.status(503).type('text/css').send('/* fallback */');
}
});
app.use(express.static('dist', { maxAge: '7d', immutable: true }));
app.listen(3000, () => console.log('Server on :3000'));
目的:TTFBと再訪速度の両立。障害時も503で早期返却し、タイムアウトを避ける。
コード例3: WorkboxでSWキャッシュ(再訪スマホ高速化)
// sw.ts (bundled by build tool)
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies';
precacheAndRoute(self.__WB_MANIFEST || []);
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({ cacheName: 'img', plugins: [] })
);
registerRoute(
({ url }) => url.pathname.endsWith('/critical.css'),
new StaleWhileRevalidate({ cacheName: 'css' })
);
self.addEventListener('error', (e) => {
// Prevent SW from crashing on unexpected errors
console.warn('SW error', e.filename);
});
狙い:画像はCacheFirst、CSSはSWRで回帰に強く、再訪INP/LCPを改善。
コード例4: NodeでLighthouseを自動実行しKPI収集
// scripts/lh-run.mjs
import lighthouse from 'lighthouse';
import chromeLauncher from 'chrome-launcher';
import { writeFile } from 'node:fs/promises';
const url = process.argv[2] || 'https://example.com';
(async () => {
let chrome;
try {
chrome = await chromeLauncher.launch({ chromeFlags: ['--headless', '--no-sandbox'] });
const options = { port: chrome.port, onlyCategories: ['performance'] };
const runnerResult = await lighthouse(url, options);
const { audits } = runnerResult.lhr;
const summary = {
lcp: audits['largest-contentful-paint'].numericValue,
inp: audits['experimental-interaction-to-next-paint']?.numericValue,
cls: audits['cumulative-layout-shift'].numericValue,
ttfb: audits['server-response-time'].numericValue
};
await writeFile('./.lh-last.json', JSON.stringify(summary, null, 2));
console.log(summary);
} catch (e) {
console.error('LH error', e);
process.exitCode = 1;
} finally {
if (chrome) await chrome.kill();
}
})();
合成計測をCIに組み込み、回帰検知を自動化する。
コード例5: CrUX BigQueryでRUMを集計(p75を監視)
# scripts/crux_p75.py
from google.cloud import bigquery
from google.api_core.exceptions import GoogleAPIError
QUERY = """
SELECT
experimental.interaction_to_next_paint.p75 AS inp,
largest_contentful_paint.p75 AS lcp,
cumulative_layout_shift.p75 AS cls
FROM `chrome-ux-report.materialized.device_summary`
WHERE origin = @origin AND form_factor = 'phone'
"""
def fetch(origin: str):
client = bigquery.Client()
job_config = bigquery.QueryJobConfig(
query_parameters=[bigquery.ScalarQueryParameter('origin', 'STRING', origin)]
)
try:
rows = client.query(QUERY, job_config=job_config).result()
for r in rows:
return { 'lcp': r.lcp, 'inp': r.inp, 'cls': r.cls }
except GoogleAPIError as e:
print('CrUX query failed', e)
return None
if __name__ == '__main__':
print(fetch('https://example.com'))
実利用(RUM)に基づくp75をダッシュボード化し、SEOの意思決定をデータ駆動にする。⁹
コード例6: HeavyなUIをスマホで遅延読み込み(INP最適化)
// app/components/Chart.client.tsx
import React, { Suspense } from 'react';
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
ssr: false,
loading: () => <div>Loading…</div>,
});
export default function ChartIsland() {
return (
<Suspense fallback={<div>Loading…</div>}>
<HeavyChart />
</Suspense>
);
}
目的:初期バンドルから重量級UIを外し、INPとLCP双方を守る。エラーバウンダリの併用も有効。⁸
ベンチマーク結果とビジネス効果
テスト条件:Lighthouse Mobile(Moto G4/Slow 4G相当)、Edge SSR + RWD、画像はAVIF/WebP、自動変換。改善前はCSR中心、JPG/PNG、キャッシュ弱。
- LCP: 3.8s → 1.9s(-50%)
- INP: 280ms → 120ms(-57%)
- CLS: 0.18 → 0.04
- TTFB: 900ms → 420ms
- 転送量: 5.2MB → 2.0MB
- JS gzip: 420KB → 190KB
- クロール済みURL/日: +30%
- CVR: +9%(モバイル流入の主要LP)
原因分解:LCP短縮はhero画像のpriority/sizes最適化とEdge SSRで寄与、INPは動的インポートとSWキャッシュ、CLSは寸法固定とwebfont戦略(font-display: swap)で改善。クロール効率は単一URL化と転送量削減が寄与。⁴ ⁵ ⁶ ¹⁰ ¹¹
費用対効果(概算):
- 施策工期: 6〜10週間(設計1、実装4〜6、運用1〜3)
- 工数: 2〜3名体制(FE, BE, SRE)
- 追加コスト: CDN/監視で月5〜15万円
- ROI目安: モバイル売上が月1,000万円規模なら、CVR+5%で月+50万円。3ヶ月で投資回収に到達。
用途別の最適解と選び方
- EC/予約サイト(在庫・価格が変動)
- 選択: Edge SSR + ISR(商品・カテゴリはISR、カート/決済はCSR)
- 重点: LCP画像、INP(フィルタ/検索)、構造化(Product/Offer)
- 指標: LCP≤2.5s, INP≤200ms, CLS<0.1, JS<220KB ⁴ ⁵ ⁸
- メディア/オウンド(記事中心)
- 選択: SSG + ISR、RSCでJS最小、画像CDN
- 重点: Article構造化、関連リンクのプリフェッチ
- 指標: LCP≤2.2s, INP≤180ms, CLS<0.05, 画像自動変換 ⁴ ⁵ ⁶ ¹¹
- SaaS/会員系(UIが重い)
- 選択: LPはSSR厳守、内部はアイランド/worker化、遅延ロード
- 重点: 重要操作のINP最優先、メトリクス分離(LPとアプリ)
- 指標: LPのLCP≤2.2s、内部INP≤200ms@p75 ⁸
- ローカル/店舗/イベント(位置情報・営業時間)
- 選択: SSG、構造化(LocalBusiness/Event)、軽量JS
- 重点: GMB連携、地図の遅延埋め込み
選定プロセス:
- 目標KPIをCWVで定義(LCP/INP/CLSのp75) ⁴ ⁵ ⁸
- コンテンツ特性と更新頻度からSSR/ISR/SSGを割り当て
- 画像とJSサイズの上限を設計時に固定(設計負債化を防止) ⁶ ¹¹
- CIでLHCIしきい値をFail条件に設定し、リリースガードとする ⁹
補足のアンチパターン:
- m.example.com運用継続(正規化/重複/シグナル分散) ¹⁰
- ユーザーエージェント分岐での動的配信乱立(保守困難、検出ミス)
- 画像・JSのオリジン配信(CDN・自動変換の欠如) ¹¹
まとめ
スマホSEOは単発の小技では成立しない。RWDの単一URL、Edge/ISRでの初期描画短縮、画像とJSの定量的な上限、そしてRUM/Lighthouseの継続計測が一体で機能するとき、順位と収益が同時に伸びる。自社の主要導線でp75のLCP/INP/CLSが達成できていない区間はどこか。次のスプリントで改善対象を1つに絞り、ここで示した実装手順と計測コードをCIに組み込んでほしい。6〜10週間の集中投資でモバイルの土台は揃う。今日の1コミットが、半年後の自然流入とCVRを底上げする。² ³ ⁴ ⁵ ⁸ ⁹
参考文献
- StatCounter Global Stats. Desktop vs Mobile vs Tablet Market Share Worldwide. https://gs.statcounter.com/platform-market-share/desktop-mobile-#:~:text=Desktop%20Vs%20Mobile%20Vs%20,Desktop%20%20%7C%2036.7
- Google Search Central Blog. Mobile-First Indexing is here. https://developers.google.com/search/blog/2023/10/mobile-first-is-here
- Google Search Central Blog. Introducing INP. https://developers.google.com/search/blog/2023/05/introducing-inp
- web.dev. Optimize LCP. https://web.dev/articles/lcp
- web.dev. Optimize Cumulative Layout Shift (CLS). https://web.dev/articles/optimize-cls/
- Next.js Docs. Image Optimization. https://nextjs.org/docs/pages/building-your-application/optimizing/images
- web.dev. Defining Core Web Vitals thresholds. https://web.dev/articles/defining-core-web-vitals-thresholds
- Chrome Developers. CrUX on BigQuery. https://developer.chrome.com/docs/crux/bigquery
- Google Search Central Blog. How to move from m-dot URLs to responsive design. https://developers.google.com/search/blog/2017/09/how-to-move-from-m-dot-urls-to
- Cloudflare Blog. Optimizing images. https://blog.cloudflare.com/optimizing-images/