モバイル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 |
| 主要KPI | LCP ≤ 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と合算して季節要因や端末差も把握します。
実装手順(番号付き)
- 計測の確立: RUM(web-vitals)を導入し、ビルド時にPSI/LHCIを回す。KPI閾値を設定(LCP≤2.5s、INP≤200ms、CLS≤0.1)。
- 画像パイプライン: sharpでAVIF/WebPを生成し、CMS連携とキャッシュ設計(immutable)。Heroはpriority配信。
- レンダリング戦略: クリティカル領域SSR/SSG、非クリティカルは遅延。動的インポートでJS削減。
- ネットワーク最適化: HTTP/2/3とBrotli、Early Hints、preloadヒント。CDNでTLS1.3を有効化。
- キャッシュ制御: HTMLは短期+再検証、静的は長期immutable。VaryとClient Hintsを適切に付与。
- 構造化データ: JSON-LDをSSRで出力。パンくず・FAQ・Productを優先実装。
- クロール最適化: robots/sitemapを整備。重要ページはJS不要で主要内容が見えるようSSR。
- リダイレクト最適化: HTTPS化、1ホップで完結。hreflangの相互参照を確認。
- INP対策: 大きなリスナー分割、Web Worker活用、メインスレッド占有回避。
- CI/CD統合: 閾値に基づくゲーティング、ベンチマークをダッシュボード化。
ベンチマーク結果とビジネス効果
あるECサイト(PV月間500万、モバイル比率72%)の改善例を示します。計測はステージング環境で、同一コンテンツ・同一CDN構成で実施。
| 指標(モバイル) | 改善前 | 改善後 | 差分 |
|---|---|---|---|
| LCP | 3.4s | 1.9s | -1.5s |
| INP | 280ms | 140ms | -140ms |
| CLS | 0.18 | 0.04 | -0.14 |
| TTFB | 1.1s | 0.65s | -0.45s |
| 転送JS | 420KB | 230KB | -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日後の流入と収益の上振れを設計していきましょう。
参考文献
- Visual Capitalist. Desktop vs. Mobile Global Web Traffic
- Google Search Central Blog. Mobile-first indexing is now complete
- Google Search Central Blog. Introducing INP for Core Web Vitals
- ScientiaMobile. Mobile Site Visitors Abandon More Than 3 Seconds
- Google Search Central. Core Web Vitals
- web.dev. Time to First Byte (TTFB)
- Google Search Central. Mobile-First Indexing Best Practices
- Chrome Developers. Chrome UX Report (CrUX)
- web.dev. Interaction to Next Paint (INP)
- web.dev. Largest Contentful Paint (LCP)
- web.dev. Optimize Long Tasks
- RFC 8297. An HTTP Status Code for Early Hints
- web.dev. Lazy-loading images
- Google Search Central. Introduction to structured data
- Google Search Central Blog. Page experience update
- Google Search Central. JavaScript SEO basics
- Google Search Central. Tell Google about localized versions of your page (hreflang)
- Google Search Central Blog. HTTPS as a ranking signal
- Lighthouse CI. Documentation
- PageSpeed Insights API. Get started