3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsでの広告ブロッカー検知システムの作り方

Last updated at Posted at 2025-12-01

概要

現代のウェブサイト運営において、広告収入はコンテンツ提供の重要な柱となっています。しかし、広告ブロッカーの普及率が上昇する中、サイト運営者は収益減少という課題に直面しています。本記事では、Next.jsとReactを使用した広告ブロッカー検知システムの実装について解説します。

この記事を読むとわかること

  • 広告ブロッカーを検知する3つの実装手法
  • 検知結果をUIで伝えるUX設計

背景と重要性

広告ブロッカーの現状

  • 普及率: 世界的に25-40%のユーザーが広告ブロッカーを使用(2024年データ)
  • 影響: 広告収入の30-50%がブロックされる可能性
  • トレンド: Braveブラウザなどのプライバシー重視ブラウザの増加

検知システムの必要性

広告ブロッカー検知システムは、単なる技術的対策ではなく、以下の戦略的価値を提供します:

  • 収益最適化: 広告収入の最大化
  • ユーザーエンゲージメント: サイト運営の透明性向上
  • 持続可能なビジネスモデル: 無料コンテンツ提供の継続

アーキテクチャ概要

システム構成

image.png

検知アルゴリズムの詳細

3層検知アプローチ

高精度検知のために、3つの異なる手法を組み合わせています:

1. ネットワークベース検知

const detectViaNetwork = async () => {
  try {
    const response = await fetch(
      'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js',
      {
        method: 'HEAD',
        mode: 'no-cors',
        cache: 'no-store',
        signal: AbortSignal.timeout(3000) // タイムアウト設定
      }
    );
    return false; // リクエスト成功 = ブロッカーなし
  } catch (error) {
    if (error.name === 'AbortError') {
      return false; // タイムアウト = ネットワーク問題
    }
    return true; // ブロックされた = ブロッカーあり
  }
};

特徴:

  • Google AdSenseスクリプトをテスト
  • CORS制限を回避するためのno-corsモード
  • タイムアウト処理で誤検知防止

2. DOM操作ベース検知

const detectViaDOM = () => {
  return new Promise(resolve => {
    const testDiv = document.createElement('div');
    testDiv.innerHTML = ' ';
    testDiv.className = 'adsbox ad-container ad-placement';
    testDiv.style.position = 'absolute';
    testDiv.style.left = '-999px';

    document.body.appendChild(testDiv);

    setTimeout(() => {
      const style = window.getComputedStyle(testDiv);
      const isHidden =
        testDiv.offsetHeight === 0 ||
        testDiv.offsetWidth === 0 ||
        testDiv.offsetParent === null ||
        style.display === 'none' ||
        style.visibility === 'hidden';

      document.body.removeChild(testDiv);

      resolve(isHidden);
    }, 100);
  });
};

特徴:

  • 複数の広告クラス名を使用した包括的検知
  • CSSベースの隠蔽検知
  • 即時実行可能

3. グローバルプロパティ検知

const detectViaGlobals = () => {
  const adBlockerIndicators = [
    () => typeof window.canRunAds === 'boolean' && !window.canRunAds,
    () => window.isAdBlockActive === true,
  ];

  return adBlockerIndicators.some(check => {
    try {
      return check();
    } catch {
      return false;
    }
  });
};

特徴:

  • 一般的な広告ブロッカー拡張機能のグローバル変数チェック
  • エラーハンドリングで安全な実行

検知精度の最適化

const comprehensiveDetection = async () => {
  const results = await Promise.allSettled([
    detectViaNetwork(),
    Promise.resolve(detectViaDOM()),
    Promise.resolve(detectViaGlobals())
  ]);

  // 少なくとも2つの方法で検知された場合に陽性
  const positiveResults = results.filter(result =>
    result.status === 'fulfilled' && result.value === true
  );

  return positiveResults.length >= 2;
};

戦略:

  • 並列実行でパフォーマンス向上
  • 複数一致による誤検知低減
  • Promise.allSettledによる堅牢性

ユーザー体験設計

段階的アプローチ

  1. 初回検知: 即時警告(侵襲的だが効果的)
  2. ユーザー選択: 明確な選択肢提供
  3. 再表示制御: 24時間クールダウン(localStorage使用)

UI/UXのベストプラクティス

.adBlockOverlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  animation: fadeIn 0.3s ease-out;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

設計原則:

  • 視覚的階層: アイコン→タイトル→説明→アクション
  • 感情的アプローチ: 共感を呼ぶ表現
  • 行動喚起: 明確な次のステップ

アクセシビリティ考慮

  • ARIAラベルとロールの適切な使用
  • キーボードナビゲーション対応
  • スクリーンリーダー対応テキスト

実装の高度なテクニック

パフォーマンス最適化

// 遅延読み込みとメモ化
const useAdBlockDetection = () => {
  return useMemo(() => {
    if (typeof window === 'undefined') return false;

    // 検知結果のキャッシュ(sessionStorage使用)
    const cached = sessionStorage.getItem('adblock-detected');
    if (cached) {
      return JSON.parse(cached);
    }

    // 実際の検知処理
    return comprehensiveDetection().then(result => {
      sessionStorage.setItem('adblock-detected', JSON.stringify(result));
      return result;
    });
  }, []);
};

エラーハンドリング

const safeDetection = async () => {
  try {
    const result = await comprehensiveDetection();
    return result;
  } catch (error) {
    console.warn('AdBlock detection failed:', error);
    // 検知失敗時はブロッカーなしと仮定(安全側)
    return false;
  }
};

テスト戦略

// 検知機能のユニットテスト
describe('AdBlock Detection', () => {
  test('should detect network blocking', async () => {
    global.fetch = jest.fn().mockRejectedValue(new Error('Blocked'));
    const result = await detectViaNetwork();
    expect(result).toBe(true);
  });

  test('should handle DOM blocking', () => {
    // DOM操作のモックテスト
    const mockElement = document.createElement('div');
    mockElement.style.display = 'none';
    // テストロジック
  });
});

潜在的な課題と解決策

誤検知の防止

  • ネットワーク問題: タイムアウトとリトライロジック
  • ブラウザ拡張: 一般的な拡張機能の除外
  • プライバシーツール: Brave Shieldsなどの識別

法的・倫理的考慮

  • GDPR準拠: 同意ベースのアプローチ
  • 透明性: 検知目的の明確な開示
  • ユーザーの権利: 強制的なブロック回避

技術的限界

  • 検知回避: 高度なブロッカーの進化対応
  • パフォーマンス: 検知処理の最適化
  • 互換性: 様々なブラウザ・デバイス対応

高度な拡張機能

A/Bテスト統合

const variants = {
  gentle: {
    title: '広告ブロッカーを検知しました',
    message: 'サイト運営にご協力いただけますか?'
  },
  direct: {
    title: '広告ブロッカーが有効です',
    message: 'コンテンツ提供のため、広告表示にご協力ください'
  }
};

アナリティクス連携

const trackDetection = (detected: boolean, userAction: string) => {
  analytics.track('adblock_detection', {
    detected,
    action: userAction,
    page: window.location.pathname,
    timestamp: new Date().toISOString()
  });
};

動的コンテンツ制限

const ContentGate = ({ children, adBlockDetected }) => {
  if (adBlockDetected) {
    return <BlurredContent>{children}</BlurredContent>;
  }
  return children;
};

終わりに

Braveの広告ブロックは

  • CSS を書き換えない
  • グローバル変数を持たない
  • ネットワーク制御をトラッカー単位で行う

ので、これでは実装は不完全です。
いずれBrave対策版を出すかもしれません。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?