Article

スマホ SEO対策を比較|違い・選び方・用途別の最適解

高田晃太郎
スマホ 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 gzipdynamic import、RSC実行遅延抑制
キャッシュSW + CDNStale-While-Revalidate再訪高速化
構造化JSON-LD(Product/Article等)Headに注入SERP機能拡張
計測RUM + 合成(CrUX/BigQuery, LHCI) ⁹CrUX/BigQuery, LHCI継続改善

選定原則:単一URLのRWDを土台に、Edge/ISRで初期表示を最適化し、画像・JS最小化でCWVを満たす。計測はRUMで現実の端末・回線を追跡し、Lighthouse CIで回帰検出を自動化する。⁹

実装手順とコード

実装は「描画パスの短縮→転送削減→計測運用」の順で進める。

  1. レンダリング方針をSSR/ISRに統一し、CSRは対話必須領域に限定
  2. RWDを徹底(単一URL、viewport適正化、m-dot統合) ¹⁰
  3. 画像最適化(AVIF/WebP、正しいsizes、fetchpriority) ⁶ ¹¹
  4. JS分割(dynamic import、RSC/島構成)、優先度制御
  5. キャッシュ(CDN + Service Worker)、HTTP/2ヒント
  6. 構造化データとメタ(canonical、robots、lang)
  7. 計測基盤(RUM + Lighthouse CI + CrUX) ⁹
  8. 監視/アラート(しきい値逸脱で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連携、地図の遅延埋め込み

選定プロセス:

  1. 目標KPIをCWVで定義(LCP/INP/CLSのp75) ⁴ ⁵ ⁸
  2. コンテンツ特性と更新頻度からSSR/ISR/SSGを割り当て
  3. 画像とJSサイズの上限を設計時に固定(設計負債化を防止) ⁶ ¹¹
  4. 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を底上げする。² ³ ⁴ ⁵ ⁸ ⁹

参考文献

  1. 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
  2. Google Search Central Blog. Mobile-First Indexing is here. https://developers.google.com/search/blog/2023/10/mobile-first-is-here
  3. Google Search Central Blog. Introducing INP. https://developers.google.com/search/blog/2023/05/introducing-inp
  4. web.dev. Optimize LCP. https://web.dev/articles/lcp
  5. web.dev. Optimize Cumulative Layout Shift (CLS). https://web.dev/articles/optimize-cls/
  6. Next.js Docs. Image Optimization. https://nextjs.org/docs/pages/building-your-application/optimizing/images
  7. web.dev. Defining Core Web Vitals thresholds. https://web.dev/articles/defining-core-web-vitals-thresholds
  8. Chrome Developers. CrUX on BigQuery. https://developer.chrome.com/docs/crux/bigquery
  9. 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
  10. Cloudflare Blog. Optimizing images. https://blog.cloudflare.com/optimizing-images/