メインコンテンツまでスキップ

広告配置仕様

Free プラン / 匿名ユーザーに対して Google AdSense 広告を表示する際の、対象ページ・スロット配置・広告形式・CLS 対策を定義する。

本仕様は ビジネスモデル・課金設計 の「ad-free を有料プランの付加価値にする」方針を実装に落とし込んだものであり、親 Issue #230 の実装着手前に配置設計を確定させる目的で作成する。

1. 概要

  • 広告プロダクト: Google AdSense(手動配置 / <ins class="adsbygoogle">
  • Auto Ads は採用しない。理由は以下の 3 点:
    1. CLS 制御: 手動配置なら広告枠の minHeight を固定できる
    2. 配置品質: Auto Ads はコンテンツ構造を無視してオーバーレイを挿入するため UX を損なう
    3. ポリシー準拠: コンテンツ比率・誤クリック誘導を自前でコントロールする必要がある
  • Publisher ID: ca-pub-2848807786857797(公開情報、apps/web/public/ads.txt に記載)
  • 収益化戦略上の位置付け: Free プランの持続可能性を広告収益で担保し、有料プラン(Plus / Pro)は「広告非表示」を付加価値として訴求する(apps/web/src/views/home/ui/pricing-section/pricing-section.tsxadFree 行と整合)

2. 表示対象ユーザー

判定マトリクス

ユーザー状態plan広告表示
匿名(未ログイン)表示
ログイン済 Freefree表示
ログイン済 Plusplus非表示
ログイン済 Propro非表示

判定ロジック

  • 新設する useShouldShowAds() フックで判定する(実装は #232
  • 内部で既存の useUserSubscription()apps/web/src/features/user-subscription/model/use-user-subscription/use-user-subscription.ts)を利用する
  • 匿名ユーザー時は useUserSubscription() の query が無効(enabled=false)になるため、フォールバックとして Free 相当で扱う
  • プランの定義は packages/shared/src/user/user-subscription/user-subscription.tsplanSchema = z.enum(["free", "plus", "pro"]) に準拠

3. 対象ページとスロット配置

広告を表示するのは /search/videos/[videoId] の 2 ページのみ。

3.1 /search(検索結果ページ)

  • 主要導線: /(ヘッダー検索 or CTA)→ /search?q=...、または検索履歴・直接リンク
  • ページ特性: サービス内で最長のスクロールを生むページ。検索結果を Grid 形式で無限スクロール風に表示するため、ユーザー滞在時間が長い
  • 配置の狙い: スクロール深度が深いユーザーを自然な位置で捕捉する

スロット一覧

ID位置導線トリガー形式最小高
search-result-midroll結果グリッドの 3 件目と 4 件目の間(以降 5 件ごとに繰り返し)検索実行 → 結果をスクロール中に自然視界に入るインフィード広告280px
search-result-bottom結果リスト末尾、ページネーション直前スクロール終端レスポンシブディスプレイ280px

注記

  • 空状態(0 件ヒット、未検索)時は search-result-bottom のみ表示し、search-result-midroll は表示しない
  • search-result-midroll の繰り返し頻度は AdSense の「1 ページあたりの広告比率」ポリシーを侵さない範囲で調整する(初期値 5 件ごと、実測で調整)
  • インフィード広告は検索結果カードと視覚的に近いスタイルにするが、「広告」ラベルを上部に必ず表示する(ポリシー準拠、ユーザー錯誤防止)

3.2 /videos/[videoId](動画詳細ページ)

  • 主要導線: /search → 結果カードクリック → /videos/[videoId]
  • ページ特性: YouTube プレイヤー + 字幕ビューア + マッチハイライトで構成される縦長ページ
  • 配置の狙い: プレイヤー視聴開始時と字幕読了後の 2 点で広告に触れさせる

スロット一覧

ID位置導線トリガー形式最小高
video-detail-below-playerYouTube プレイヤーと字幕ビューアの間再生開始時にスクロールなしで視界に入るレスポンシブディスプレイ250px
video-detail-below-subtitle字幕ビューア末尾、関連動画セクション上字幕読了後、次アクション前マルチプレックス300px

注記

  • video-detail-below-playerプレイヤー直下ゆえに誤クリックのリスクが高いため、上下に margin: 16px 以上の余白を設ける
  • 字幕ビューア内部にミドルイン広告を挿入する案は、字幕読書体験を分断するため 初回実装では見送る
  • 右サイドバーが存在しても、初回実装では広告枠として利用しない(レイアウトの視認性を優先)

3.3 対象外ページとその理由

ページ除外理由
/(ホーム)CTA / Pricing への導線が密集しており、広告で離脱率を増やしたくない
/account/*集中度の高い短尺ページ、ユーザーの作業体験を妨げたくない
/login認証ページは摩擦を最小化する
/admin/*運用管理用、対象外
/privacy, /terms, /contact規約系・低流量、表示メリットが小さい
/changelog低流量、運用コストに見合わない

4. 広告形式と CLS 対策

4.1 使用する広告形式

  • レスポンシブディスプレイ広告: data-ad-format="auto" + data-full-width-responsive="true"。画面幅に応じて 320x50〜970x250 の範囲で自動選択
  • インフィード広告: 検索結果グリッドに自然に溶け込むカード風スタイル。data-ad-format="fluid" + data-ad-layout-key を AdSense 管理画面で発行
  • マルチプレックス広告: data-ad-format="autorelaxed"。関連コンテンツ風に複数広告をグリッド表示

4.2 CLS 対策

スロットごとに minHeight を固定し、広告ロード前後でレイアウトシフトが発生しないようにする。リポジトリの .claude/rules/web-performance.md で定めた「動的コンテンツの高さ確保」方針に準拠。

スロットminHeight
search-result-midroll280px
search-result-bottom280px
video-detail-below-player250px
video-detail-below-subtitle300px
  • 広告が非配信(AdSense の在庫切れ、広告ブロッカー有効等)の場合でも、コンテナ要素は maintain する(空白のまま高さを保つ)
  • loading="lazy"adsbygoogle.js が自動付与するため、コンポーネント側での明示は不要
  • 「広告」ラベルは日本語で「広告」または英語で「Advertisement」を上部に表示する(ポリシー準拠)

5. 実装方針

実装詳細は #232 の PR で決めるが、本仕様で以下の原則を固定する:

  • スクリプト読み込み: adsbygoogle.jsapps/web/src/app/[locale]/layout.tsx<head>next/scriptstrategy="afterInteractive" として 1 回だけ読み込む
  • 共通コンポーネント: AdSlotapps/web/src/shared/ui/ad-slot/)を新設。props で slotId / format / minHeight を受け取る
  • 非表示時の挙動: useShouldShowAds() が false の場合、AdSlotnull を返す(空 div も出さない)
  • 環境変数: NEXT_PUBLIC_ADSENSE_CLIENT_ID が未設定の場合は本番ビルドでも広告をレンダーしない。#231 の審査完了前でもローカル開発・Preview デプロイを阻害しないため

6. プライバシー・コンプライアンス

AdSense CMP(同意管理プラットフォーム)

  • 選択: Google CMP、3 択(同意する / 同意しない / オプションを管理する)
  • 適用範囲: EEA(欧州経済領域) / 英国 / スイスのユーザーのみに表示される。日本・米国等のユーザーには CMP は表示されない
  • 拒否時の挙動: 非パーソナライズ広告(NPA / Non-Personalized Ads)にフォールバックし、広告配信は継続される。収益は約 30〜70% 低下するが、ゼロにはならない

プライバシーポリシー

  • apps/web/src/app/[locale]/privacy/page.tsx に AdSense 利用の記載を追加する必要がある(実装 PR である #233 / #234 着手時に対応)
  • 記載内容: 配信事業者、Cookie 利用目的、オプトアウト方法

7. 検証観点

計測と閾値検証は #235 で実施する。

観点閾値 / 方法
CLS< 0.1(Lighthouse / Chrome DevTools Performance)
LCP広告追加前後で +200ms 以内(SpeedInsights の 75 パーセンタイル)
プラン別表示匿名 / Free / Plus / Pro の 4 状態で 2 ページを巡回、表示・非表示が仕様通りか目視確認
ポリシー「広告」ラベル、誤クリック誘導なし、コンテンツ比の妥当性
広告ブロッカーuBlock Origin 有効時にレイアウト崩れが発生しないこと

8. 関連 Issue / 参考資料

Issue

既存ファイル

  • apps/web/src/views/home/ui/pricing-section/pricing-section.tsxadFree 比較行
  • apps/web/src/features/user-subscription/model/use-user-subscription/use-user-subscription.ts — プラン判定のベース
  • packages/shared/src/user/user-subscription/user-subscription.tsplanSchema
  • apps/web/messages/ja.jsoncomparison.rows.adFree

外部ドキュメント