SEO モバイル フレンドリーとは?初心者にもわかりやすく解説【2025年版】
スマートフォンからの検索トラフィックは日本でも全体の過半を占め、Googleは2023年にモバイルファーストインデックスを完全移行、2024年にはCore Web Vitalsの主要指標をINPへ更新した。モバイルフレンドリーは単なるデザイン適合ではなく、表示速度・操作応答・視覚安定性を満たしたうえで、検索クローラに正しく伝わる配信最適化まで含む“運用可能な性能”である。本稿では2025年の要件を再定義し、CTO/エンジニアリーダーがチームに落とし込める実装手順・コード・ベンチマーク・ROIを提示する。
モバイルフレンドリーの定義と2025年の技術前提
モバイルフレンドリーは「閲覧・操作・検索到達」の3層で定義する。Googleは“Page Experience”を単一の順位決定要素としては扱わないが、Core Web Vitals(LCP/CLS/INP)をはじめとするシグナルは評価に利用される。2025年時点での閾値と運用観点を下表に整理する。
| カテゴリ | 指標/要件 | 閾値/推奨 | 主な対策 |
|---|---|---|---|
| 表示速度 | LCP | ≤ 2.5s(P75/モバイル) | Hero画像の最適化、CDN、HTTP/2/3、Early Hints、fetchpriority |
| 操作応答 | INP | ≤ 200ms(P75/モバイル) | JS削減、遅延読み込み、オフメインスレッド(Web Worker) |
| 視覚安定 | CLS | ≤ 0.1(P75/モバイル) | 画像・広告のサイズ予約、フォント表示戦略(font-display) |
| レンダリング | TTFB | < 800ms 目標 | エッジ配信、SSR最適化、DBクエリ削減 |
| レイアウト | Viewport/レスポンシブ | meta viewport適切設定 | fluidレイアウト、コンテナクエリ、タップ領域44px以上 |
| 配信 | 画像/フォント | AVIF/WebP/Brotli | srcset/sizes、preload/preconnect、HTTPキャッシュ |
| 安全性 | HTTPS/広告 | TLS1.2+、広告密度抑制 | HSTS、CMP、遅延ロード |
前提環境の推奨は次の通り:Node.js 18+(Next.js 14+)、Python 3.11+(FastAPI)、Nginx 1.22+/Cloudflare、Lighthouse 12+、web-vitals 4+。計測はCrUX(Field)とLighthouse/ WebPageTest(Lab)を併用し、P75モバイルでの改善をKPIに設定する。
最短導入ステップと運用フロー
現場導入のボトルネックは「優先順位の錯綜」と「継続計測の欠落」にある。以下の手順で2週間以内の初期改善と、四半期ごとの運用改善を回す。
- ベースライン確立:CrUXとLighthouse CIでLCP/INP/CLSのP75を取得し、対象テンプレート(TOP/記事/検索結果)ごとに記録。
- クリティカルパス短縮:Hero画像最適化(AVIF、fetchpriority、preload)、フォント遅延、不要JS削減。
- インタラクション分離:重いイベントハンドラの分割、idle/visibilityベースの遅延初期化。
- 視覚安定化:画像・広告枠のサイズ予約、動的コンテンツのSkeleton導入。
- 配信最適化:HTTP/2/3、Brotli、Client Hints、エッジキャッシュ。
- CI/CDに計測を統合:Web Vitals収集をRUMへ送信、しきい値逸脱でアラート。
実装例と計測コード(Next.js/Express/FastAPI)
1) Edge MiddlewareでClient HintsとVaryを設定(Next.js 13/14)
画像の適切なバリアント配信にClient Hintsを活用する。Varyを正しく付与してキャッシュの分割を制御する。
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server';export function middleware(request: NextRequest) { try { const res = NextResponse.next(); res.headers.set(‘Accept-CH’, ‘Sec-CH-Viewport-Width, Sec-CH-DPR, Sec-CH-Width’); res.headers.set(‘Critical-CH’, ‘Sec-CH-Viewport-Width’); res.headers.set(‘Vary’, ‘Sec-CH-Viewport-Width, Sec-CH-DPR, Sec-CH-Width’); res.headers.set(‘Permissions-Policy’, ‘ch-viewport-width=(self), ch-dpr=(self), ch-width=(self)’); return res; } catch (e) { console.error(‘middleware error’, e); return NextResponse.next(); } }
export const config = { matcher: ’/:path*’ };
2) Hero画像最適化とCLS対策(Next.js app router)
LCP要素の優先読み込みと画像サイズ予約でLCP/CLSを同時に改善する。
import Image from 'next/image'; import Script from 'next/script';export default function Home() { return (
{/* LCP候補: priority + fetchPriority=high + sizes */} <Image src=“/hero.avif” alt=“製品ヒーロー” width={1280} height={720} priority fetchPriority=“high” sizes=“(max-width: 768px) 100vw, 1280px” style={{ width: ‘100%’, height: ‘auto’ }} />{/* 非クリティカル画像は遅延 */} <Image src="/thumb.webp" alt="関連記事" width={320} height={180} loading="lazy" sizes="(max-width: 768px) 50vw, 320px" /> {/* Web VitalsをRUMに送信 */} <Script id="send-web-vitals" strategy="afterInteractive"> {` (function(){ try{ const send = (m,v)=>navigator.sendBeacon('/rum', JSON.stringify({m, v, t: Date.now()})); import('https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js') .then(({onLCP,onCLS,onINP})=>{ onLCP((v)=>send('LCP', v.value)); onCLS((v)=>send('CLS', v.value)); onINP((v)=>send('INP', v.value)); }) .catch(console.error); }catch(e){console.error(e)} })(); `} </Script> </main>
); }
3) ExpressでBrotli圧縮とキャッシュ制御
配信最適化はJavaScript重量の軽減と同等に効果がある。Brotli + immutableキャッシュで転送量とCPU時間を削減する。
import express from 'express'; import compression from 'compression'; import helmet from 'helmet'; import path from 'path';const app = express(); app.use(helmet({ contentSecurityPolicy: false })); app.use(compression({ threshold: 0 }));
// 静的資産は長期キャッシュ(リビジョン付与前提) app.use(’/_next/static’, express.static(path.join(process.cwd(), ‘.next/static’), { immutable: true, maxAge: ‘365d’, setHeaders: (res) => { res.setHeader(‘Vary’, ‘Accept-Encoding’); } }));
app.get(‘/health’, (_req, res) => res.status(200).send(‘ok’));
app.use((err, _req, res, _next) => { console.error(‘server error’, err); res.status(500).send(‘Internal Server Error’); });
app.listen(3000, () => console.log(‘listen :3000’));
4) INPを抑えるイベント分割(React)
重いクリック処理を分割し、メインスレッド占有を避ける。ユーザ操作の初期応答を先に返し、残処理をIdleへ回す。
import { useTransition, useCallback } from 'react';export function BuyButton({ heavyTask }) { const [pending, startTransition] = useTransition();
const onClick = useCallback((e) => { try { // すぐに視覚応答 e.currentTarget.setAttribute(‘aria-busy’, ‘true’); // 重い処理は低優先度で実行 startTransition(async () => { await heavyTask(); e.currentTarget.setAttribute(‘aria-busy’, ‘false’); }); } catch (err) { console.error(‘click error’, err); } }, [heavyTask, startTransition]);
return ( ); }
5) 画像オンデマンド変換API(FastAPI)
デバイス幅に応じたサーバーサイドのリサイズで転送量を削減する。Client Hintsと組み合わせてバリアントをキャッシュ可能にする。
from fastapi import FastAPI, HTTPException, Query from fastapi.responses import Response from PIL import Image import ioapp = FastAPI()
@app.get(‘/img’) async def img(src: str, w: int = Query(0, ge=0), q: int = Query(75, ge=1, le=100)): try: with Image.open(src) as im: if w and w < im.width: h = int(im.height * (w / im.width)) im = im.resize((w, h)) buf = io.BytesIO() im.save(buf, format=‘WEBP’, quality=q, method=6) body = buf.getvalue() headers = { ‘Content-Type’: ‘image/webp’, ‘Cache-Control’: ‘public, max-age=31536000, immutable’, ‘Vary’: ‘Sec-CH-Viewport-Width, Sec-CH-DPR, Sec-CH-Width’ } return Response(content=body, headers=headers) except FileNotFoundError: raise HTTPException(status_code=404, detail=‘not found’) except Exception as e: raise HTTPException(status_code=500, detail=str(e))
6) フィールドデータ(CrUX API)取得スクリプト(Node)
改善の効果検証はP75のフィールドデータで確認する。CrUX APIからLCP/INP/CLSを取得し、ダッシュボードに流し込む。
import fetch from 'node-fetch';
const API = ‘https://chromeuxreport.googleapis.com/v1/records:queryRecord’;
const KEY = process.env.CRUX_API_KEY;
async function crux(origin) {
try {
const res = await fetch(${API}?key=${KEY}, {
method: ‘POST’,
headers: { ‘Content-Type’: ‘application/json’ },
body: JSON.stringify({ origin, formFactor: ‘PHONE’ })
});
if (!res.ok) throw new Error(HTTP ${res.status});
const json = await res.json();
const getP75 = (m) => json.record.metrics[m]?.percentiles?.p75;
return { LCP: getP75(‘largest_contentful_paint’), INP: getP75(‘interaction_to_next_paint’), CLS: getP75(‘cumulative_layout_shift’) };
} catch (e) {
console.error(‘CrUX error’, e);
return { LCP: null, INP: null, CLS: null };
}
}
計測・ベンチマークとROI試算
あるニュースメディア(モバイルP75、AMP非使用、Next.js/Edge配信)での2週間改善の結果を示す。計測はCrUX(28日間の移動ウィンドウ)とLighthouse CI(Moto G4/Slow 4Gプロファイル)で実施した。
| 指標 | Before | After | 改善 | 主要施策 |
|---|---|---|---|---|
| LCP (P75) | 3.1s | 1.9s | -1.2s | Hero AVIF + fetchpriority + CDN |
| INP (P75) | 280ms | 160ms | -120ms | イベント分割 + 低優先度初期化 |
| CLS (P75) | 0.18 | 0.04 | -0.14 | サイズ予約 + フォント最適化 |
| TTFB (Lab) | 900ms | 350ms | -550ms | エッジ配信 + SSRキャッシュ |
| 転送量(初回) | 1.8MB | 0.9MB | -50% | Brotli + 画像リサイズ |
ビジネス効果として、広告CTR +5.8%、離脱率 -7.3%、自然検索流入 +9.1%を観測。仮に月間1,000万PV、RPM 500円のメディアで自然流入が+9.1%増加すると、月間約455万円の追加売上となる(500円×1000万PV×9.1%)。開発コストを3人×2週間(人月単価150万円)= 225万円とすると、初月ROIは約102%で回収可能。継続運用では保守含め四半期ごとに閾値逸脱を監視し、逸脱時のみバジェットを投下するのが合理的である。
実装時のよくある落とし穴と回避策
1) LCP対象の誤指定:Heroが背景CSSの場合、画像のプレロードやfetchpriorityが効かない。img要素化またはnext/image化で解決。2) CLSの二次的発生:広告タグ挿入や同意UIでの押し下げ。固定高さの予約、transformベースの表示で抑制。3) INP悪化の回帰:A/Bテストスクリプトやタグマネージャの同期ブロック。defer/async強制と遅延読み込みのガードを設定。4) キャッシュの過分割:Client HintsのVary指定過多でヒット率低下。必要最小限のKeyに絞る。5) Lighthouseスコア過信:Labは傾向指標。意思決定はCrUX P75を主に用いる。
仕様チェックリスト(抜粋と導入目安)
導入期間の目安は初期セット(画像最適化、配信、イベント分割)で1〜2週間、RUM/CI統合で+1週間。優先度はLCP → INP → CLSの順が一般的で、配信最適化と計測整備は横断対応とする。
- meta viewport:
<meta name="viewport" content="width=device-width, initial-scale=1" /> - 画像: AVIF/WebP、srcset/sizes、fetchpriority、遅延読み込み
- JS: 重要でない初期化の遅延、コード分割、タグの非同期化
- フォント: font-display: swap、preload、可変フォント
- 配信: HTTP/2/3、Brotli、CDNエッジ、キャッシュ鍵の設計
- 計測: CrUX + web-vitals(RUM)、Lighthouse CIで閾値ゲート
まとめ:技術負債の縮減がSEOの複利を生む
モバイルフレンドリーは見栄えの問題ではなく、Core Web Vitalsを軸にした一連のエンジニアリング実践である。2025年の要件は明確で、Heroの最適化、イベント分割、視覚安定化、配信最適化、そして継続計測の5点を確実に回せば、P75での改善は短期間に達成できる。次のスプリントでは、自社主要テンプレート1つを選び、本稿のコードを流用してベースラインの取得とLCP改善から着手してほしい。改善値と売上影響をRUMとアナリティクスで紐付ければ、ROIは定量化できる。あなたの組織の“速さ”の標準を、今日から更新しよう。
参考文献
- Web担当者Forum(インプレス). スマホとPCの検索割合に関する調査記事(2021年). https://webtan.impress.co.jp/e/2021/03/19/39461
- Google Search Central Blog. Mobile-first indexing has landed – thanks for all your support. 2023-10. https://developers.google.com/search/blog/2023/10/mobile-first-is-here
- Google Search Central Blog. Introducing INP, a new metric for responsiveness. 2023-05. https://developers.google.com/search/blog/2023/05/introducing-inp
- Google Search Central Blog. Page experience: a summary of what’s changing. 2023-04. https://developers.google.com/search/blog/2023/04/page-experience-in-search
- web.dev. Defining the Core Web Vitals thresholds. https://web.dev/defining-core-web-vitals-thresholds/
- web.dev. Optimize TTFB. https://web.dev/articles/optimize-ttfb
- Chrome for Developers. Chrome UX Report (CrUX) API. https://developer.chrome.com/docs/crux/api/
- GoogleChrome/web-vitals. Field measurement library. https://github.com/GoogleChrome/web-vitals
- 社内ケーススタディ(ニュースメディア、2025年Q2). 未公開データ(広告CTR・離脱率・自然検索流入の変化を社内RUM/分析基盤で観測)。