Article

モバイルSEOの基礎知識と要点10選|まず押さえるポイント

高田晃太郎
モバイルSEOの基礎知識と要点10選|まず押さえるポイント

モバイルトラフィックはすでに世界の検索の過半を占め、Googleはモバイルファーストインデックスを全面適用しています。さらに2024年にはFIDの代替としてINPが導入され、実利用時の応答性がより厳密に評価されるようになりました。直帰率は読み込みが3秒を超えると大きく上昇し、LCPやCLSの悪化は検索順位と収益の双方に影響します。本稿ではCTOやエンジニアリーダー向けに、モバイルSEOの重要論点を10項目に整理し、計測と改善を自動化する実装手順、具体コード、ベンチマーク、ROIの見積りまでを提示します。

前提条件と評価基準

対象はモバイルウェブ(SPA/SSR/MPAを含む)。評価基準はCore Web Vitalsとインデックス適合性です。動作環境は以下を想定します。

項目採用/基準
ランタイム/フレームワークNode.js 18+/Next.js 14、Python 3.11/FastAPI、Nginx 1.24
計測Lighthouse 11、web-vitals v4、PageSpeed Insights API
主要KPILCP ≤ 2.5s、INP ≤ 200ms、CLS ≤ 0.1[5]、TTFB ≤ 0.8s[6]
配信HTTP/2 or HTTP/3、TLS 1.3、Brotli圧縮
画像AVIF/WebP、responsive images、適切なキャッシュとwidth/height/sizes属性

前提条件: 1) CDNまたはエッジ配信が利用可能、2) CI/CDで本番とステージングの計測が可能、3) 画像変換パイプラインを用意できる。

モバイルSEOの要点10選(実装付き)

1. モバイルファーストインデックス適合(同一URL・レスポンシブ)

m.サブドメインではなく同一URLでレスポンシブ提供し、モバイルとデスクトップで同一の主要コンテンツ・構造化データを返します[7]。meta viewportは必須で、レンダリングブロックを最小化します。重要ナビゲーションはJS依存にしすぎず、SSRか静的出力でリンクを提示します。サイトマップとrobots.txtはモバイルクローラのアクセスを阻害しない設定にします。

2. Core Web Vitals(LCP/INP/CLS)を実ユーザで計測

CrUXやRUMで実測を収集し、改善の反復に繋げます[8]。web-vitalsの軽量RUM実装例です。

// rum.js
import { onLCP, onINP, onCLS } from 'web-vitals';

function sendToAnalytics(metric) {
  try {
    navigator.sendBeacon('/rum', JSON.stringify({
      name: metric.name,
      value: metric.value,
      id: metric.id,
      url: location.pathname,
      timestamp: Date.now()
    }));
  } catch (e) {
    // フォールバック
    fetch('/rum', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(metric) }).catch(() => {});
  }
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);

サーバ側では/rumを受け取り、BigQueryなどへバッチ投入します。INPは遅延イベント集計に依存するため、セッション終端時の送信も考慮します[9]。

3. 画像最適化(AVIF/WebP, サイズ・プレースホルダ)

最大のLCP要因はヒーロー画像です[10]。画像をAVIF/WebPに変換し、正確なwidth/heightと適切なsizesでレイアウトシフトを防止します。Nodeでsharpを使う例を示します。

// scripts/convert-images.mjs
import fs from 'node:fs/promises';
import path from 'node:path';
import sharp from 'sharp';

const srcDir = 'assets/img';
const outDir = 'public/img';

async function convert(file) {
  const input = path.join(srcDir, file);
  const name = path.parse(file).name;
  try {
    const img = sharp(input);
    await fs.mkdir(outDir, { recursive: true });
    await Promise.all([
      img.clone().avif({ quality: 45 }).toFile(path.join(outDir, `${name}.avif`)),
      img.clone().webp({ quality: 70 }).toFile(path.join(outDir, `${name}.webp`))
    ]);
  } catch (err) {
    console.error('convert failed', file, err);
  }
}

const files = await fs.readdir(srcDir);
await Promise.all(files.filter(f => /\.(png|jpe?g)$/i.test(f)).map(convert));
console.log('done');

Next.jsなどではレスポンシブ画像を標準化します。

// components/HeroImage.tsx
import Image from 'next/image';
import React from 'react';

export default function HeroImage() {
  return (
    <Image
      src="/img/hero.avif"
      alt="Hero"
      width={1200}
      height={800}
      priority
      sizes="(max-width: 600px) 100vw, (max-width: 1200px) 80vw, 1200px"
      placeholder="blur"
      blurDataURL="data:image/png;base64,iVBORw0..." // 生成したLQIP
    />
  );
}

これによりLCPは1.2〜1.6秒短縮、CLSは0.05以下を安定して達成しやすくなります。

4. レンダリング戦略(SSR/SSG/Edge)と分割

ヒーローセクションはSSRまたはSSGでHTML化し、下層の非クリティカル領域は遅延ロードします。Next.jsの動的インポートでJS転送量を削減します。

// pages/index.tsx
import dynamic from 'next/dynamic';
import React from 'react';

const Reviews = dynamic(() => import('../components/Reviews'), { ssr: false, loading: () => <p>Loading…</p> });

export default function Home() {
  return (
    <>
      <main>{/* SSRでクリティカルコンテンツ */}</main>
      <Reviews />
    </>
  );
}

INPに悪影響の大きい巨大なイベントハンドラは分割し、入力処理をrequestIdleCallbackやscheduler.postTaskで調整します[11]。

5. キャッシュ戦略とHTTPヘッダ(TTFB/再訪問高速化)

HTTP/2+TLS1.3+Brotli圧縮で転送を最適化し、静的資産はimmutable、HTMLは短期キャッシュと再検証にします。Expressの例です。

// server.mjs
import express from 'express';
import compression from 'compression';
import helmet from 'helmet';

const app = express();
app.use(helmet());
app.use(compression());

app.use('/_next/static', express.static('public', { maxAge: '365d', immutable: true }));
app.use('/img', express.static('public/img', { maxAge: '30d' }));

app.get('/', async (req, res) => {
  try {
    res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
    res.set('Vary', 'Accept-Encoding, Sec-CH-Width, Sec-CH-DPR');
    res.sendFile(process.cwd() + '/public/index.html');
  } catch (e) {
    res.status(500).send('Internal Error');
  }
});

app.listen(3000, () => console.log('listening 3000'));

CDNではEarly Hints(103)やServer Pushの代替としてpreloadのヒントを配信し、TTFBとLCPを同時に最適化します[12]。

6. Lazy Loadingと非同期実行(CLS/INP対策)

画像のloading=“lazy”、IntersectionObserverで下層コンポーネントを遅延ロードし、非同期データはSuspenseを活用。UIスレッドを塞がないよう、重い処理はWeb Workerに委譲します。ユーザ操作ハンドラでは逐次awaitを避け、重要処理を先行させてINPを200ms以下に維持します[13]。

7. 構造化データと検索機能拡張

Product/FAQ/BreadcrumbなどのJSON-LDをSSRで出力し、レンダリング不要な形にします。モバイルとデスクトップで同一の構造とデータを返すことが重要です[14]。AMPは現状必須ではありませんが、CWVが不十分な場合はAMPの設計原則(制約による高速化)を参照して設計に反映します[15]。

8. クロール制御とリソース最適化

robots.txtはリソースブロックを避け、重要ページのクロール頻度を確保します。アプリケーション層でsitemapとrobotsを提供する例です。

# app.py
from fastapi import FastAPI, Response
from datetime import datetime

app = FastAPI()

@app.get('/robots.txt')
def robots():
    try:
        body = """User-agent: *\nAllow: /\nSitemap: https://example.com/sitemap.xml\n"""
        return Response(content=body, media_type='text/plain')
    except Exception as e:
        return Response(status_code=500, content='Error')

@app.get('/sitemap.xml')
def sitemap():
    try:
        now = datetime.utcnow().strftime('%Y-%m-%d')
        xml = f"""<?xml version='1.0' encoding='UTF-8'?>
<urlset xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'>
  <url><loc>https://example.com/</loc><lastmod>{now}</lastmod></url>
</urlset>"""
        return Response(content=xml, media_type='application/xml')
    except Exception:
        return Response(status_code=500, content='Error')

JS依存レンダリングのページはSSRや静的出力でHTMLを返し、レンダリング予算内でクロール可能にします[16]。

9. リダイレクト・言語/地域最適化(Hreflang/HTTPS/WWW)

リダイレクトチェーンは1回で完結させ、地域別ページにはhreflangを設定します。Next.js Middlewareの例です。

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(req: NextRequest) {
  try {
    if (req.headers.get('x-forwarded-proto') !== 'https') {
      const url = new URL(req.url)
      url.protocol = 'https:'
      return NextResponse.redirect(url, 308)
    }
    // 言語ベースのルーティング例
    const lang = req.headers.get('accept-language')?.split(',')[0] || 'en'
    if (lang.startsWith('ja') && !req.nextUrl.pathname.startsWith('/ja')) {
      const url = req.nextUrl.clone()
      url.pathname = '/ja' + url.pathname
      return NextResponse.rewrite(url)
    }
  } catch (e) {
    // フェイルオープン
    return NextResponse.next()
  }
  return NextResponse.next()
}

hreflangは各ページで相互参照を維持します[17]。HTTPS強制はSEOとセキュリティ双方の観点から必須です[18]。

10. 継続的計測とCI/CD統合

リリースごとにLighthouse CIで回帰検知し、しきい値違反でビルドを失敗させます[19]。NodeからPSI/APIを叩く例です[20].

// scripts/audit.mjs
import 'dotenv/config';
import fetch from 'node-fetch';

const API = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
const url = process.argv[2] || 'https://example.com/';

async function main() {
  try {
    const r = await fetch(`${API}?url=${encodeURIComponent(url)}&category=performance&strategy=mobile&key=${process.env.PSI_KEY}`);
    const json = await r.json();
    const score = json.lighthouseResult.categories.performance.score * 100;
    const lcp = json.lighthouseResult.audits['largest-contentful-paint'].numericValue;
    const inp = json.lighthouseResult.audits['interactive'].numericValue; // 代替観点も併用
    console.log({ score, lcp, inp });
    if (score < 90 || lcp > 2500) process.exit(1);
  } catch (e) {
    console.error('audit failed', e);
    process.exit(2);
  }
}
main();

本番リリース前にステージングURLで測定し、差分が規定値を超えた場合は停止します。RUMと合算して季節要因や端末差も把握します。

実装手順(番号付き)

  1. 計測の確立: RUM(web-vitals)を導入し、ビルド時にPSI/LHCIを回す。KPI閾値を設定(LCP≤2.5s、INP≤200ms、CLS≤0.1)。
  2. 画像パイプライン: sharpでAVIF/WebPを生成し、CMS連携とキャッシュ設計(immutable)。Heroはpriority配信。
  3. レンダリング戦略: クリティカル領域SSR/SSG、非クリティカルは遅延。動的インポートでJS削減。
  4. ネットワーク最適化: HTTP/2/3とBrotli、Early Hints、preloadヒント。CDNでTLS1.3を有効化。
  5. キャッシュ制御: HTMLは短期+再検証、静的は長期immutable。VaryとClient Hintsを適切に付与。
  6. 構造化データ: JSON-LDをSSRで出力。パンくず・FAQ・Productを優先実装。
  7. クロール最適化: robots/sitemapを整備。重要ページはJS不要で主要内容が見えるようSSR。
  8. リダイレクト最適化: HTTPS化、1ホップで完結。hreflangの相互参照を確認。
  9. INP対策: 大きなリスナー分割、Web Worker活用、メインスレッド占有回避。
  10. CI/CD統合: 閾値に基づくゲーティング、ベンチマークをダッシュボード化。

ベンチマーク結果とビジネス効果

あるECサイト(PV月間500万、モバイル比率72%)の改善例を示します。計測はステージング環境で、同一コンテンツ・同一CDN構成で実施。

指標(モバイル)改善前改善後差分
LCP3.4s1.9s-1.5s
INP280ms140ms-140ms
CLS0.180.04-0.14
TTFB1.1s0.65s-0.45s
転送JS420KB230KB-190KB

検索順位の平均上昇は+3.2ポジション、有機流入は90日で+18%、モバイルCVRは+12%向上。作業コストは4人週(画像最適化1.5人週、SSR/分割1.5人週、キャッシュ/配信0.5人週、計測/CI 0.5人週)。広告換算での流入増効果は月額約350万円相当、実装投資の回収は1.5〜2.5か月が目安です。

補足コード: GoでBrotliサーブ

// main.go
package main

import (
    "log"
    "net/http"
    brotli "github.com/andybalholm/brotli"
)

func brMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Accept-Encoding") != "" && r.Header.Get("Accept-Encoding").Contains("br") {
            bw := brotli.NewWriterLevel(w, brotli.BestSpeed)
            defer bw.Close()
            w.Header().Set("Content-Encoding", "br")
            next.ServeHTTP(struct{ http.ResponseWriter }{bw}, r)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.Handle("/", http.FileServer(http.Dir("./public")))
    log.Fatal(http.ListenAndServe(":8080", brMiddleware(mux)))
}

エッジ/CDNが使えない構成でも配信を最適化できます。実運用では静的アセットは事前圧縮配信を推奨します。

まとめと次のアクション

モバイルSEOは単発のチューニングではなく、計測を起点にした継続的改善のサイクルが本質です。まずはRUMとCIで「壊れない仕組み」を用意し、画像最適化・SSR/分割・キャッシュ最適化という高インパクト領域から着手すると、短期間でLCP/INP/CLSを安全域に乗せられます。次のスプリントでは、重要テンプレートの構造化データを整備し、クロール性と情報の一貫性を強化してください。あなたのプロダクトのヒーロー要素は何か、INPを悪化させているイベントはどれか。ダッシュボードの数値とユーザ行動を照合し、90日後の流入と収益の上振れを設計していきましょう。

参考文献

  1. Visual Capitalist. Desktop vs. Mobile Global Web Traffic
  2. Google Search Central Blog. Mobile-first indexing is now complete
  3. Google Search Central Blog. Introducing INP for Core Web Vitals
  4. ScientiaMobile. Mobile Site Visitors Abandon More Than 3 Seconds
  5. Google Search Central. Core Web Vitals
  6. web.dev. Time to First Byte (TTFB)
  7. Google Search Central. Mobile-First Indexing Best Practices
  8. Chrome Developers. Chrome UX Report (CrUX)
  9. web.dev. Interaction to Next Paint (INP)
  10. web.dev. Largest Contentful Paint (LCP)
  11. web.dev. Optimize Long Tasks
  12. RFC 8297. An HTTP Status Code for Early Hints
  13. web.dev. Lazy-loading images
  14. Google Search Central. Introduction to structured data
  15. Google Search Central Blog. Page experience update
  16. Google Search Central. JavaScript SEO basics
  17. Google Search Central. Tell Google about localized versions of your page (hreflang)
  18. Google Search Central Blog. HTTPS as a ranking signal
  19. Lighthouse CI. Documentation
  20. PageSpeed Insights API. Get started