Article

SEO モバイル フレンドリーとは?初心者にもわかりやすく解説【2025年版】

高田晃太郎
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週間以内の初期改善と、四半期ごとの運用改善を回す。

  1. ベースライン確立:CrUXとLighthouse CIでLCP/INP/CLSのP75を取得し、対象テンプレート(TOP/記事/検索結果)ごとに記録。
  2. クリティカルパス短縮:Hero画像最適化(AVIF、fetchpriority、preload)、フォント遅延、不要JS削減。
  3. インタラクション分離:重いイベントハンドラの分割、idle/visibilityベースの遅延初期化。
  4. 視覚安定化:画像・広告枠のサイズ予約、動的コンテンツのSkeleton導入。
  5. 配信最適化:HTTP/2/3、Brotli、Client Hints、エッジキャッシュ。
  6. 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 io

app = 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 }; } }

crux(‘https://example.com’).then(console.log);

計測・ベンチマークと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は定量化できる。あなたの組織の“速さ”の標準を、今日から更新しよう。

参考文献

  1. Web担当者Forum(インプレス). スマホとPCの検索割合に関する調査記事(2021年). https://webtan.impress.co.jp/e/2021/03/19/39461
  2. 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
  3. Google Search Central Blog. Introducing INP, a new metric for responsiveness. 2023-05. https://developers.google.com/search/blog/2023/05/introducing-inp
  4. 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
  5. web.dev. Defining the Core Web Vitals thresholds. https://web.dev/defining-core-web-vitals-thresholds/
  6. web.dev. Optimize TTFB. https://web.dev/articles/optimize-ttfb
  7. Chrome for Developers. Chrome UX Report (CrUX) API. https://developer.chrome.com/docs/crux/api/
  8. GoogleChrome/web-vitals. Field measurement library. https://github.com/GoogleChrome/web-vitals
  9. 社内ケーススタディ(ニュースメディア、2025年Q2). 未公開データ(広告CTR・離脱率・自然検索流入の変化を社内RUM/分析基盤で観測)。