メインコンテンツへスキップ

Next.jsでiOS・Android・PCを正確に判定する方法|User-Agentの落とし穴と実務での使い分け

··3241 文字·7 分

個人開発で「喫煙所検索アプリ」をリリースした際、iOS版のApp StoreリンクとAndroid版のGoogle Playリンクを出し分ける必要がありました。「ユーザーのデバイスに応じてリダイレクト先を変えたい」——シンプルに見えるこの要件が、実装してみると意外な落とし穴だらけでした。

iPadからのアクセスがなぜかPCと判定されるBot(クローラー)のアクセスで想定外のリダイレクトが発生するClient ComponentではUser-Agentが取得できない——。

この記事では、Next.js(App Router)でのUser-Agent判定について、公式ドキュメントには載っていない実務で本当に嵌るポイントを中心に解説します。


User-Agentとは
#

User-Agent(ユーザーエージェント) とは、ブラウザがサーバーにリクエストを送る際に自動的に付与するHTTPヘッダーです。OS、ブラウザ、デバイスの情報が含まれています。

# iPhoneの場合
Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 ...

# Androidの場合
Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 ...

# PCの場合(macOS Chrome)
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ...

この文字列をパースすることで、アクセス元のデバイスを判定できます。


Next.jsでUser-Agentを取得する3つの方法
#

Next.js(App Router)でUser-Agentを取得する方法は3つあります。

方法実行タイミング主な用途
Server Componentページ描画時(サーバー側)デバイスに応じたUI出し分け
Middlewareリクエスト受信時(エッジ)デバイスに応じたリダイレクト
API RouteAPI呼び出し時デバイス情報をJSONで返す

方法1:Server Componentで判定する
#

最もシンプルな方法です。headers() 関数でリクエストヘッダーを取得します。

import { headers } from "next/headers";

// デバイス判定ユーティリティ
function getDeviceType(userAgent: string): "ios" | "android" | "web" {
  if (/iPhone|iPod/.test(userAgent)) return "ios";
  if (/Android/.test(userAgent)) return "android";
  return "web";
}

export default async function Page() {
  const headersList = await headers();
  const ua = headersList.get("user-agent") ?? "";
  const device = getDeviceType(ua);

  return (
    <div>
      <p>あなたのデバイス: {device}</p>
      {device === "ios" && (
        <a href="https://apps.apple.com/app/xxx">App Storeで開く</a>
      )}
      {device === "android" && (
        <a href="https://play.google.com/store/apps/details?id=xxx">
          Google Playで開く
        </a>
      )}
    </div>
  );
}

⚠️ headers()Server Component専用のAPI です。Client Component("use client" を宣言したコンポーネント)では使用できません。


方法2:Middlewareでリダイレクトする
#

ユーザーがページにアクセスした瞬間にリダイレクトしたい場合は、Middlewareを使います。

// middleware.ts(プロジェクトルートに配置)
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  // /app ページへのアクセスのみ対象
  const ua = request.headers.get("user-agent") ?? "";

  if (/iPhone|iPod/.test(ua)) {
    return NextResponse.redirect(
      "https://apps.apple.com/app/your-app-id"
    );
  }

  if (/Android/.test(ua)) {
    return NextResponse.redirect(
      "https://play.google.com/store/apps/details?id=your.app.id"
    );
  }

  // PC / その他はそのまま表示
  return NextResponse.next();
}

export const config = {
  matcher: ["/app/:path*"], // 特定パスのみに適用
};

matcher を必ず設定する理由
#

matcher を設定しないと、画像やCSS、APIルートなどすべてのリクエストにMiddlewareが実行されます。これはパフォーマンスの低下やGooglebotのクロール阻害につながるため、必ず対象パスを限定してください。


方法3:ユーティリティ関数として共通化する
#

実務では判定ロジックを関数化し、複数箇所から呼び出せるようにします。

// lib/device.ts

export type DeviceType = "ios" | "android" | "tablet" | "web";

export function getDeviceType(userAgent: string): DeviceType {
  const ua = userAgent.toLowerCase();

  // iPadの判定(後述の落とし穴を参照)
  if (/ipad/.test(ua)) return "tablet";

  // iPhoneの判定
  if (/iphone|ipod/.test(ua)) return "ios";

  // Androidタブレットの判定("Android" を含むが "Mobile" を含まない)
  if (/android/.test(ua) && !/mobile/.test(ua)) return "tablet";

  // Androidスマホの判定
  if (/android/.test(ua)) return "android";

  return "web";
}

実務で嵌る落とし穴3選
#

落とし穴1:iPadがPCと判定される問題
#

iPadOS 13以降、iPadのSafariはデスクトップ版のUser-Agentを送信するようになりました。

# iPadOS 13以降のUA(macOSと区別がつかない!)
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 ...

つまり、User-Agentだけでは iPadとMacBookを区別できません

対策:タッチイベントとの併用
#

// Client Component側で判定する場合
function isIPad(): boolean {
  return (
    navigator.userAgent.includes("Macintosh") &&
    "ontouchend" in document
  );
}

サーバーサイドでは完全な判定は不可能なため、iPadの判定が必須な場合はClient Componentでの判定を併用してください。

落とし穴2:Botのアクセスでリダイレクトしてしまう
#

GooglebotなどのクローラーもUser-Agentを持っています。Middleware でBot判定を行わないと、Googleのクローラーまでアプリストアにリダイレクトされ、ページがインデックスされないという事態になります。

// Botを除外する判定を追加
function isBot(ua: string): boolean {
  return /Googlebot|bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|facebookexternalhit|Twitterbot/i.test(ua);
}

export function middleware(request: NextRequest) {
  const ua = request.headers.get("user-agent") ?? "";

  // Botはリダイレクトしない
  if (isBot(ua)) return NextResponse.next();

  // 以下、通常のデバイス判定...
}

落とし穴3:User-Agentは偽装可能
#

User-Agentはブラウザの設定やDevToolsで簡単に変更できます。そのため、セキュリティに関わる判定(認証・認可)にUser-Agentを使ってはいけません

あくまでUXの最適化(UIの出し分け、ストアへの誘導)に留めるべきです。


Client Hints APIとの比較
#

User-Agentの代替として、GoogleはChrome 89以降で User-Agent Client Hints(UA-CH) を推進しています。

比較項目User-AgentClient Hints API
ブラウザ対応すべてのブラウザChrome/Edge系のみ
情報の精度OS・ブラウザ・デバイスが混在構造化されたデータで取得
プライバシー詳細情報が常に送信される必要な情報のみリクエスト
将来性段階的に情報が削減される予定Googleが推進する後継仕様
// Client Hints の利用例(Middleware)
export function middleware(request: NextRequest) {
  const platform = request.headers.get("sec-ch-ua-platform"); // "Android", "iOS", "macOS" 等
  const isMobile = request.headers.get("sec-ch-ua-mobile");   // "?1" or "?0"

  // Chrome/Edge系でのみ取得可能
  if (platform && isMobile) {
    // 構造化データで正確に判定できる
  }
}

現時点での結論:Safari(iOS)がClient Hintsに対応していないため、iOSの判定にはUser-Agentが依然として必須です。Chrome系ブラウザのみを対象とする場合はClient Hintsを優先し、全ブラウザ対応が必要な場合はUser-Agentをベースにしましょう。


Chrome DevToolsでテストする方法
#

実機がなくても、Chrome DevToolsでUser-Agentを変更してテストできます。

  1. F12(またはCmd+Option+I)でDevToolsを開く
  2. Network conditions タブを開く(見つからない場合は ... → More tools)
  3. User agent セクションで「Use browser default」のチェックを外す
  4. プルダウンから任意のデバイス(iPhone, Android等)を選択
  5. ページをリロードして動作を確認

これにより、サーバーサイドの判定ロジックが正しく動作するか、実機なしで検証できます。


まとめ
#

ポイント内容
取得方法Server Component: headers() / Middleware: request.headers
判定対象iPhone, Android, iPad, PC, Bot
注意点iPadOS 13以降はMacと同じUA。Bot除外は必須
テストChrome DevToolsのNetwork conditionsでUA変更
将来Client Hints APIが後継だがSafari未対応

User-Agentは万能ではありませんが、正しく使えばUXの大幅な向上につながります。落とし穴を理解した上で、適材適所で活用してください。


よくある質問(FAQ)
#

Q. Client ComponentでUser-Agentを取得できますか?
#

navigator.userAgent でクライアント側のUser-Agentを取得できます。ただし、SSR時にはwindowオブジェクトが存在しないため、useEffect 内で取得する必要があります。

Q. User-Agent判定の精度はどの程度ですか?
#

一般的なiPhone・Androidスマホの判定は99%以上の精度で可能です。ただし、iPadOS 13以降のiPadや、カスタムブラウザ(Brave等)では誤判定が発生する可能性があります。

Q. Next.js Pages Routerでも同じ方法が使えますか?
#

Pages Routerでは getServerSidePropscontext.req.headers['user-agent'] でUser-Agentを取得できます。Middlewareの使い方はApp Routerと同じです。

著者
ゆーふー
Web開発、インフラ、AI技術に興味があるエンジニアです。日々の学びを記録しています。

関連記事

👤 運営者プロフィール

運営者プロフィール画像

ゆーふー

メガベンチャーで働く現役Webエンジニア(歴約2年)。
フロントエンドからインフラ構築、セキュリティ対策まで、実務で得た「現場のリアルな技術知見」を発信しています。