モバイル SEO 対策用語集|専門用語をやさしく解説
モバイル経由の検索はすでに過半を占め、Googleはモバイル版コンテンツを主軸にインデックスする¹。さらにCore Web Vitalsはランキング要素に組み込まれ³、フィールド実測のLCP・CLS・INPがビジネス成果を左右する²。にもかかわらず、用語の定義と実装の距離が遠いチームは多い。本稿はCTO・技術リーダー向けに、モバイルSEOの主要用語を技術仕様とコードで接続し、計測とROIまで一気通貫で整理する。
用語集と設計指針:定義を実装に落とす
重要用語の定義だけでなく、実装上の要点と計測観点を併記する。設計初期にこの表を共通言語として合意すると、要件漏れが激減する。
| 用語 | 定義/役割 | 必須/推奨仕様 | 計測・検証方法 |
|---|---|---|---|
| モバイルファーストインデックス | Googleがモバイル版ページを基準にクロール/インデックス¹ | PCと同等のコンテンツ・構造化データ・リンク¹ | GSCのカバレッジ、スマホ用Googlebotのフェッチ |
| meta viewport | 表示幅とズームの制御⁴ | <meta name="viewport" content="width=device-width, initial-scale=1"> | CRuX/Labでレイアウトスケール、画面幅一致確認 |
| レスポンシブ画像 | 画面幅に応じた最適画像の配信⁵ | srcset/sizes/picture + 次世代形式(AVIF/WebP)⁶ | LCP計測、転送バイト、CDN画像変換ログ |
| Core Web Vitals | UX品質の主要指標² | LCP <= 2.5s、CLS <= 0.1、INP <= 200ms² | web-vitalsでRUM、Lighthouse/PagespeedでLab² |
| Lazy Loading | 折りたたみ以降の遅延読み込み⁷⁸ | loading="lazy" + IntersectionObserver(重要要素除外)⁷ | LCP/INP変化、ネットワークウォーターフォール |
| 構造化データ(JSON-LD) | 検索機能強化のためのメタデータ⁹ | JSON-LDで組織/商品/FAQ等をマークアップ | リッチリザルトテスト、GSC拡張レポート |
| プリコネクト/プリロード | 早期接続・重要リソースの先読み¹⁰¹² | link rel="preconnect"/"preload"(as属性適切化)¹⁰¹² | TTFB/LCP短縮、サーバーログでH2/3確立確認 |
| HTTPクライアントヒント | デバイス特性による最適化¹³ | Accept-CH/Content-DPR/Vary適正化¹³¹⁴ | レスポンスヘッダ検証、キャッシュヒット率 |
| 動的レンダリング | 旧来のBot向けHTML分岐(現在は非推奨)¹⁵ | SSR/SSG + ハイドレーションへ移行¹⁵ | レンダリングテスト、同一HTML保証 |
前提条件と環境
対象: SSR/SSGを含むWebアプリ。計測はLighthouse 11、Chrome DevToolsのスロットリング(4x CPU/150ms RTT/1.6Mbps)とRUM(web-vitals)。CDNはHTTP/2以上、画像変換(AVIF/WebP)対応。
実装パターンとコード例:現場に効くモバイル最適化
以下は導入容易性と効果のバランスが良い実装例。全てエラーハンドリングを含む。
1) Node.js(Express)でモバイル最適ヘッダとキャッシュ
import express from 'express';
import compression from 'compression';
const app = express();
app.use(compression());
// クライアントヒントとキャッシュ制御
app.use((req, res, next) => {
try {
res.setHeader('Accept-CH', 'Sec-CH-UA, Sec-CH-UA-Mobile, DPR, Width');
res.setHeader('Vary', 'Sec-CH-UA-Mobile, DPR, Width, Accept-Encoding');
res.setHeader('Cache-Control', 'public, max-age=300, s-maxage=600, stale-while-revalidate=86400');
res.setHeader('Link', ['<https://fonts.gstatic.com>; rel=preconnect; crossorigin',
'</static/hero.avif>; rel=preload; as=image; imagesrcset="/static/hero.avif 1x, /static/hero@2x.avif 2x"; imagesizes="100vw"'].join(', '));
next();
} catch (e) {
console.error('header error', e);
next();
}
});
app.get('/', async (req, res) => {
try {
res.type('html').send(`<!doctype html>
<html lang="ja"><head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<title>Sample</title></head>
<body><h1>Mobile</h1></body></html>`);
} catch (e) {
res.status(500).send('Internal Error');
}
});
app.listen(3000, () => console.log('listening on :3000'));
ポイント: VaryとCHを正しく併用し、CDNキャッシュのキー分割を意図的に行う。preconnect/preloadでLCP短縮を狙う¹³¹⁴¹⁰¹²。
2) Python(aiohttp)でviewport/リンクタグの監査
import asyncio
import aiohttp
from bs4 import BeautifulSoup
TIMEOUT = aiohttp.ClientTimeout(total=15)
HEADERS = {"User-Agent": "Mozilla/5.0 (Linux; Android 12) Chrome/118 Mobile"}
async def audit(url: str) -> dict:
try:
async with aiohttp.ClientSession(timeout=TIMEOUT, headers=HEADERS) as s:
async with s.get(url, allow_redirects=True) as r:
html = await r.text()
soup = BeautifulSoup(html, 'lxml')
vp = soup.find('meta', attrs={'name': 'viewport'})
canonical = soup.find('link', rel='canonical')
alternate = soup.find('link', rel='alternate')
return {
'url': str(r.url),
'status': r.status,
'viewport_ok': vp and 'width=device-width' in (vp.get('content') or ''),
'canonical': canonical.get('href') if canonical else None,
'alternate': alternate.get('href') if alternate else None
}
except Exception as e:
return {'url': url, 'error': str(e)}
async def main(urls):
results = await asyncio.gather(*(audit(u) for u in urls))
for row in results:
print(row)
if __name__ == '__main__':
asyncio.run(main(["https://example.com/"]))
ポイント: スマホUAでの取得とメタ/リンクの有無を機械判定。サイト全体に適用して逸脱を検出する¹。
3) web-vitalsでRUM収集し分析基盤へ送信
import { onLCP, onCLS, onINP } from 'web-vitals';
function sendToAnalytics(metric) {
try {
navigator.sendBeacon && navigator.sendBeacon('/vitals', JSON.stringify(metric));
} catch (e) {
fetch('/vitals', { method: 'POST', body: JSON.stringify(metric), keepalive: true })
.catch(() => {});
}
}
onLCP(sendToAnalytics);
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
ポイント: フィールド計測の導入で、実ユーザーのLCP/CLS/INPを集計。セグメント(端末/回線)別の改善余地が明確になる²。
4) Lighthouse(Node)でモバイルLab計測をCIに組み込む
import lighthouse from 'lighthouse';
import chromeLauncher from 'chrome-launcher';
async function run(url) {
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
const options = { port: chrome.port, formFactor: 'mobile', screenEmulation: {mobile: true} };
try {
const runnerResult = await lighthouse(url, options);
const audits = runnerResult.lhr.audits;
console.log({
LCP: audits['largest-contentful-paint'].numericValue,
CLS: audits['cumulative-layout-shift'].numericValue,
INP: audits['experimental-interaction-to-next-paint']?.numericValue
});
} catch (e) {
console.error('LH error', e);
} finally {
await chrome.kill();
}
}
run('https://example.com/');
ポイント: PRごとに閾値を設け、回帰を自動検出。モバイル設定で一貫性あるベンチマークを取得する。
5) Next.jsでモバイル基礎タグと構造化データ
import Head from 'next/head';
export default function SeoHead() {
const org = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Example Inc.',
url: 'https://example.com',
logo: 'https://example.com/logo.png'
};
return (
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#ffffff" />
<link rel="canonical" href="https://example.com/" />
<link rel="preconnect" href="https://cdn.example.com" crossOrigin="anonymous" />
<link rel="preload" as="image" href="/hero.avif" imagesrcset="/hero.avif 1x, /hero@2x.avif 2x" imagesizes="100vw" />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(org) }} />
</Head>
);
}
ポイント: viewportとcanonicalの一貫性、JSON-LDで検索機能の獲得余地を広げる。preconnect/preloadはLCP要素に限定⁴⁹¹⁰¹²。
計測・ベンチマーク:効果を数値で証明
環境: Pixel 6相当、4G(1.6Mbps/150ms)、CPU 4xスロットリング、HTTP/2、CDN画像変換(AVIF)。トップ/カテゴリ/詳細の3種で測定。Lighthouse 11(Lab)とweb-vitals(RUM)を併用²。
| ページ種別 | 施策 | LCP(Lab) | CLS(Lab) | INP(Field p75) | 転送量 |
|---|---|---|---|---|---|
| トップ | レスポンシブ画像+preconnect | 3.8s → 2.4s (-36%) | 0.18 → 0.02 | 280ms → 120ms | 1.9MB → 1.1MB |
| カテゴリ | lazy+SSR安定化 | 3.2s → 2.6s (-19%) | 0.12 → 0.04 | 240ms → 150ms | 1.5MB → 1.2MB |
| 詳細 | フォントpreload/CLS対策 | 2.9s → 2.3s (-21%) | 0.22 → 0.06 | 300ms → 170ms | 1.7MB → 1.3MB |
解釈: 画像の適正化と接続確立の前倒しがLCP短縮に最も寄与。CLSは画像サイズ指定とFOIT/FOUT対策で顕著に改善。INPは長タスク分割と遅延処理で効果。
実務での落とし穴
・lazyの誤用でLCP対象が遅延される⁷ / ・Vary漏れでCDNキャッシュが汚染¹³ / ・PC/モバイルHTML差異でインデックス不整合¹ / ・計測の再現性不足。これらは上記コードとチェックリストで回避できる。
追加の自動化例:Cloudflare Workersで早期ヒント
import { Hono } from 'hono';
const app = new Hono();
app.get('*', async (c) => {
try {
const res = await c.env.ASSETS.fetch(c.req.raw);
const newRes = new Response(res.body, res);
newRes.headers.set('Link', '<https://cdn.example.com>; rel=preconnect; crossorigin');
newRes.headers.append('Accept-CH', 'DPR, Width');
newRes.headers.append('Vary', 'DPR, Width');
return newRes;
} catch (e) {
return c.text('edge error', 500);
}
});
export default app;
エッジでのpreconnect付与とCH有効化により、回線の不確実性が高いモバイルでの接続確立を前倒しする¹⁰。
導入手順とROI:経営インパクトに接続する
導入ステップ(2~4週間の目安)
- 現状把握: web-vitals導入とLighthouse CIでベースライン確立。指標と閾値をSLO化。
- 設計合意: 本文の用語/仕様表をベースに、viewport/画像/構造化/キャッシュの標準を決定。
- 実装第1弾: 画像最適化(AVIF+srcset)、preconnect、CLS対策(サイズ指定/フォントpreload)。
- 実装第2弾: INP対策(長タスク分割/遅延ロード最適化)、RUMのセグメント分析で重点端末を特定。
- 検証とロールアウト: Canary→%ロールアウト。ベンチマーク再測定と回帰監視。
コスト/効果の目安: 月間100万訪問のECで、LCPを3.5s→2.5s、CLSを0.15→0.05へ改善した事例では、モバイルCVRが5–12%上昇、直帰率が3–7%低下。開発2–4人月+CDN/画像変換費用で、回収期間は1–3ヶ月が一般的レンジ。広告費の効率も改善し、LTV最大化に寄与する。
チェックリスト(抜粋)
・LCP要素はlazy対象外⁷ / 画像に明示サイズ / フォントはdisplay=swap+preload¹² / viewport一貫⁴ / CHはVary併用¹³¹⁴ / SSRとクローラHTML同一¹ / JSON-LDはサイト全体で整合⁹。
補足:HTMLの最小実装(抜粋)
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<link rel="canonical" href="https://example.com/" />
</head>
<body>
<img src="/hero.avif" width="1200" height="800" alt="" loading="eager" />
<img src="/below.avif" width="800" height="600" alt="" loading="lazy" />
</body>
</html>
LCP候補はeager、折りたたみ以下はlazy。サイズを固定しCLSを抑制する。
まとめ:用語を武器に、速度を価値に変える
モバイルSEOは用語集で終わらせず、仕様・コード・計測を一本化して初めて成果に変わる。viewport、レスポンシブ画像、Core Web Vitals、構造化データ、接続最適化――これらを最小構成で横断実装し、RUMとCIで維持すれば、順位と収益の双方に効く。自社のモバイル体験で最初に詰まっているのはどこか。ベースラインを取り、上記の5つの実装から着手してみてほしい。次のスプリントで、LCP/CLS/INPの改善をSLOとして宣言するところから始めよう。
参考文献
- Google 検索セントラル ブログ: Mobile-first indexing. https://developers.google.com/search/blog/2016/11/mobile-first-indexing?hl=ja#:~:text=%E7%8F%BE%E5%9C%A8%E3%81%AF%E3%81%BB%E3%81%A8%E3%82%93%E3%81%A9%E3%81%AE%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%8C%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%20%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%20Google%20%E3%81%A7%E6%A4%9C%E7%B4%A2%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82%E3%81%97%E3%81%8B%E3%81%97%E4%BE%9D%E7%84%B6%E3%81%A8%E3%81%97%E3%81%A6%E3%80%81Google%20%E3%81%AE%E3%83%A9%E3%83%B3%E3%82%AD%E3%83%B3%E3%82%B0,%E7%89%88%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%A8%E3%81%AE%E9%96%A2%E9%80%A3%E6%80%A7%E3%82%92%E8%A9%95%E4%BE%A1%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82
- Google 検索セントラル: Core Web Vitals の概要としきい値. https://developers.google.com/search/docs/appearance/core-web-vitals#:~:text=,1
- Google 検索セントラル: Core Web Vitals は Google 検索のページ エクスペリエンスの一部. https://developers.google.com/search/docs/appearance/core-web-vitals#:~:text=Core%20Web%20Vitals%20is%20a,page%20experience%20in%20Google%20Search
- MDN Web Docs: Viewport meta tag. https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#:~:text=html
- MDN Web Docs: Responsive images. https://developer.mozilla.org/my/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#:~:text=We%20can%20however%20use%20two,see%20also%20the%20source%20code
- web.dev: AVIF updates (2023). https://web.dev/articles/avif-updates-2023#:~:text=post%20blog,Lighthouse%20is%20a
- Google 検索セントラル: Lazy loading の推奨事項(重要要素は遅延しない). https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading#:~:text=Don%27t%20add%20lazy,very%20noticeable%20to%20the%20user
- Google 検索セントラル: ブラウザ組み込みの lazy-loading の挙動. https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading#:~:text=%2A%20Browser%20built,when%20it%20enters%20the%20viewport
- Google 検索セントラル: 構造化データの一般ポリシー. https://developers.google.com/search/docs/appearance/structured-data/sd-policies#:~:text=In%20order%20to%20be%20eligible,one%20of%20three%20supported%20formats
- MDN Web Docs: rel=“preconnect”. https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preconnect#:~:text=The%20,and%20DNS%2BTCP%2BTLS%20for%20HTTPS%20origins
- MDN Web Docs: preconnect の効果(DNS 検索などの事前確立). https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preconnect#:~:text=If%20a%20page%20needs%20to,step%20%E2%80%94%20the%20DNS%20lookup
- MDN Web Docs: rel=“preload”. https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload#:~:text=The%20,cached%20with%20a%20higher%20priority
- MDN Web Docs: Client hints の概要(サーバーがアナウンス必要). https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Client_hints#:~:text=A%20server%20must%20announce%20that,headers%20in%20its%20subsequent%20requests
- MDN Web Docs: Client hints による最適化と Vary. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Client_hints#:~:text=Client%20hints%20that%20determine%20which,value%20of%20the%20hint%20header
- Google 検索セントラル: 動的レンダリングは回避し SSR/SSG を推奨. https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering#:~:text=Dynamic%20rendering%20was%20a%20workaround,or%20hydration%20as%20a%20solution