個人開発者として AdMob で月3万円前後を稼ぐような規模のアプリを運営していると、ある日突然 EEA(欧州経済領域)圏からの収益だけが急落することがあります。私自身、Rork で作って海外向けに出したアプリで、GDPR 同意周りの実装漏れを指摘する AdMob のメールを受け取ったことがあり、あのメールが届いたときの冷や汗は今でも覚えています。
原因を掘り下げていくと、ほぼ確実に UMP(User Messaging Platform)や ATT(App Tracking Transparency)の実装不備に行き着きます。Rork は本当に便利なツールで、UI から認証、データ管理まで自動生成してくれますが、広告収益化まわりの同意管理レイヤーは、現状ほぼ手動で組み込む必要があります。
ここではRork で生成したアプリに UMP と ATT を本番品質で組み込み、「GDPR 準拠」「AdMob ポリシー遵守」「収益を落とさない」の3つを同時に満たすための設計パターンを、実際に動くコードとともに解説します。
なぜ Rork アプリには同意管理の「盲点」が生まれやすいのか
Rork が自動生成する AdMob 連携コードは、多くの場合 AdMob.initializeAsync() を呼び出して広告リクエストを開始するシンプルな構成になっています。これだけでも広告は表示されますし、テストフェーズでは動作確認ができるので問題に気づきにくいのが落とし穴です。
しかし、この「素直な初期化」は以下の3つの重要なレイヤーを素通りしています。
ATT(iOS 14.5 以降) : IDFA(広告識別子)の取得には明示的なユーザー許可が必要
UMP(EEA・UK・スイス) : GDPR の下で、パーソナライズ広告を配信する前に同意ダイアログを表示する義務
初期化順序 : 同意情報を取得してから AdMob SDK を初期化しないと、非パーソナライズ広告モード(NPA)に固定され、CPM(千インプレッション単価)が2〜3割落ちる
EEA ユーザーからの収益は、実はアプリ全体収益の 30〜50% を占めるケースも多く、この「盲点」を放置すると月数万円単位で収益を落とす可能性があります。個人開発者にとっては看過できない金額です。
全体像:3つの同意レイヤーと正しい初期化順序
本番で正しく動作させるための初期化順序を、先に結論から示します。どの順番を守れば収益を落とさないかというと、以下の流れです。
アプリ起動直後に UMP の requestConsentInfoUpdate を呼び、同意状態を取得する
必要に応じて UMP の同意フォームを表示する(loadAndShowConsentFormIfRequired)
iOS の場合のみ、ATT ダイアログを表示する(requestTrackingAuthorization)
同意情報を AdMob / Firebase Analytics / RevenueCat などに伝播する
AdMob.initializeAsync() を呼び、広告リクエストを開始する
この順序を1箇所でも間違えると、広告リクエストが NPA モードで発行されてしまい、収益が目に見えて下がります。特に Rork が生成するデフォルト構成では、3と4の間に「ユーザーログイン画面」が挟まっていることが多いのですが、ここでログイン後に AdMob を初期化してしまうと、初回セッションの広告が全て NPA になってしまいます。
Step 1: UMP SDK の導入と初期化フロー
まずは UMP SDK を Rork プロジェクトに導入します。Rork は内部的に Expo/React Native を使っているので、react-native-google-mobile-ads に付属する UMP 実装を利用するのが最もスムーズです。
# UMP を含む AdMob SDK をインストール
npm install react-native-google-mobile-ads
# iOS の場合は Pod のインストールも必要
cd ios && pod install && cd ..
app.json(または app.config.ts)に AdMob の設定を追加します。Rork が生成する設定ファイルに直接追記する形です。
{
"expo" : {
"plugins" : [
[
"react-native-google-mobile-ads" ,
{
"androidAppId" : "ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" ,
"iosAppId" : "ca-app-pub-XXXXXXXXXXXXXXXX~ZZZZZZZZZZ" ,
"userTrackingUsageDescription" : "広告のパーソナライズとアプリの改善のために、端末の識別情報を使用させていただきます。"
}
]
]
}
}
次に、UMP の初期化ロジックを実装します。以下は本番で動いているコードをベースにしたもので、エラーハンドリングも含まれています。
// src/consent/initializeConsent.ts
import mobileAds, {
AdsConsent,
AdsConsentStatus,
AdsConsentDebugGeography,
} from 'react-native-google-mobile-ads' ;
export type ConsentResult = {
canRequestAds : boolean ;
consentStatus : AdsConsentStatus ;
isGDPRRegion : boolean ;
error ?: string ;
};
export async function initializeConsent () : Promise < ConsentResult > {
try {
// Step 1: 同意情報をリクエスト
// __DEV__ では EEA ユーザーとしてデバッグする
const consentInfo = await AdsConsent. requestInfoUpdate (
__DEV__
? {
debugGeography: AdsConsentDebugGeography. EEA ,
testDeviceIdentifiers: [ 'YOUR_TEST_DEVICE_ID' ],
}
: undefined ,
);
// Step 2: 必要に応じて同意フォームを表示
if (
consentInfo.isConsentFormAvailable &&
consentInfo.status === AdsConsentStatus. REQUIRED
) {
await AdsConsent. loadAndShowConsentFormIfRequired ();
}
// Step 3: 広告リクエストが可能か判定
const canRequestAds = await AdsConsent. getConsentInfo (). then (
( info ) => info.canRequestAds ?? false ,
);
return {
canRequestAds,
consentStatus: consentInfo.status,
isGDPRRegion:
consentInfo.status === AdsConsentStatus. REQUIRED ||
consentInfo.status === AdsConsentStatus. OBTAINED ,
};
} catch (error) {
// ネットワーク切断などで同意情報取得に失敗した場合
// フェイルクローズ(広告配信を停止)ではなく、
// 前回の同意状態でフォールバックするのが Google 推奨パターン
console. error ( '[Consent] Failed to initialize:' , error);
return {
canRequestAds: false ,
consentStatus: AdsConsentStatus. UNKNOWN ,
isGDPRRegion: false ,
error: error instanceof Error ? error.message : 'Unknown error' ,
};
}
}
このコードの重要なポイントは、エラー時に canRequestAds: false を返してフェイルクローズする設計です。エラーを無視して広告を出すと、GDPR 違反になる可能性があります。ネットワークエラーの場合は少し我慢して次回起動時にリトライする方が安全です。
期待される動作は以下のとおりです。
EEA ユーザーが初めて起動したとき:同意フォームが表示され、選択後に canRequestAds: true が返る
非 EEA ユーザー(日本・米国など):同意フォームは表示されず、即座に canRequestAds: true
ネットワーク切断時:canRequestAds: false で広告リクエストはスキップされる
Step 2: ATT との統合パターン
iOS では UMP に加えて ATT(App Tracking Transparency)の許可も取得する必要があります。ここで注意したいのが、UMP → ATT の順番で表示する という点です。
なぜこの順番かというと、UMP の同意フォームには「広告のパーソナライズに関する詳細な選択肢」が含まれていて、ユーザーがここで「パーソナライズを許可しない」と選んだ場合、ATT ダイアログを出す必要すらなくなるからです。先に ATT を出してしまうと、「許可したのにパーソナライズされない」という矛盾状態になり、ユーザー体験が損なわれます。
// src/consent/requestATT.ts
import { Platform } from 'react-native' ;
import {
request,
PERMISSIONS,
RESULTS,
check,
} from 'react-native-permissions' ;
export async function requestATTIfNeeded () : Promise < boolean > {
if (Platform. OS !== 'ios' ) {
return true ; // Android は ATT 不要
}
// iOS 14.5 未満は ATT API 自体がない
const iosVersion = parseFloat (Platform.Version as string );
if (iosVersion < 14.5 ) {
return true ;
}
try {
// まず現在の状態をチェック(再表示を避ける)
const currentStatus = await check ( PERMISSIONS . IOS . APP_TRACKING_TRANSPARENCY );
if (currentStatus === RESULTS . GRANTED ) return true ;
if (currentStatus === RESULTS . DENIED ) return false ;
if (currentStatus === RESULTS . BLOCKED ) return false ;
// まだ未確定なら表示
const result = await request ( PERMISSIONS . IOS . APP_TRACKING_TRANSPARENCY );
return result === RESULTS . GRANTED ;
} catch (error) {
console. error ( '[ATT] Request failed:' , error);
return false ;
}
}
このコードは「既に回答済みなら再表示しない」という挙動になっています。ATT ダイアログは iOS の仕様上、ユーザーが一度回答したら設定アプリからしか変更できません。何度も request を呼んでも再表示はされないのですが、チェックせずに呼ぶと不要な API 呼び出しが発生するので、check を先に入れておくのがベストプラクティスです。
Step 3: 地域判定と同意状態の分岐
UMP 自体が内部で地域判定をしてくれるため、アプリ側で IP 判定などを実装する必要は基本的にありません。ただし、「EEA ユーザーだけに特定の UI を出す」といった用途では、UMP の consentStatus を地域判定の代替として使えます。
// src/consent/regionAware.ts
import { AdsConsent, AdsConsentStatus } from 'react-native-google-mobile-ads' ;
export async function isLikelyGDPRRegion () : Promise < boolean > {
const info = await AdsConsent. getConsentInfo ();
// REQUIRED = 同意が必要な地域(EEA/UK/スイス)
// OBTAINED = 同意取得済み(同意が必要な地域だが既に回答済み)
// NOT_REQUIRED = 同意不要(非 EEA)
return (
info.status === AdsConsentStatus. REQUIRED ||
info.status === AdsConsentStatus. OBTAINED
);
}
自前で IP 判定をやろうとすると、VPN 利用ユーザーで誤判定したり、IP データベースの更新コストがかかったりと、メンテナンスが大変です。UMP の判定は Google が管理している最新の地域データを使っているので、これに任せるのが最も安全で安定します。
Step 4: 同意状態を AdMob / Analytics / RevenueCat に伝播する
同意情報を取得しただけでは不十分で、実際に広告や分析ツールがその情報を反映する必要があります。react-native-google-mobile-ads は UMP 連携が内部で自動化されていますが、Firebase Analytics や RevenueCat では手動で設定する箇所があります。
// src/consent/propagateConsent.ts
import analytics from '@react-native-firebase/analytics' ;
import Purchases from 'react-native-purchases' ;
import { AdsConsent } from 'react-native-google-mobile-ads' ;
export async function propagateConsent () : Promise < void > {
const consentInfo = await AdsConsent. getConsentInfo ();
const canTrack = consentInfo.canRequestAds ?? false ;
// Firebase Analytics の同意モード
// これを呼ばないと EEA ユーザーに対して勝手にデータ収集してしまう
await analytics (). setAnalyticsCollectionEnabled (canTrack);
await analytics (). setConsent ({
ad_storage: canTrack ? 'granted' : 'denied' ,
ad_user_data: canTrack ? 'granted' : 'denied' ,
ad_personalization: canTrack ? 'granted' : 'denied' ,
analytics_storage: canTrack ? 'granted' : 'denied' ,
});
// RevenueCat の属性収集も同意に基づいて制御
await Purchases. setAttributes ({
$consentGiven: canTrack ? 'true' : 'false' ,
});
if ( ! canTrack) {
// 同意がない場合は IDFA 系の属性をクリア
await Purchases. collectDeviceIdentifiers (); // 内部で null に設定
}
}
このコードの肝は、Firebase Analytics の setConsent を必ず呼ぶことです。これを呼ばないと、setAnalyticsCollectionEnabled(false) にしていても、一部のバックグラウンドイベントが送信され続けてしまう仕様があります(v18.4.0 以降で修正されていますが、古いバージョンとの互換性のため明示的に呼ぶのが安全です)。
Step 5: 同意変更 UI — 設定画面から同意を撤回する
AdMob ポリシーでは、ユーザーが「後から同意を撤回できる導線」をアプリ内に用意することが必須とされています。これを用意していないと、AdMob の審査で警告が出たり、Play Store のポリシーチェックでリジェクトされることがあります。
// src/screens/SettingsScreen.tsx
import React from 'react' ;
import { View, Text, Button, Alert } from 'react-native' ;
import { AdsConsent, AdsConsentStatus } from 'react-native-google-mobile-ads' ;
export function PrivacySettingsSection () {
const [ isLoading , setIsLoading ] = React. useState ( false );
const handleRevokeConsent = async () => {
setIsLoading ( true );
try {
// 同意状態をリセット
await AdsConsent. reset ();
// 再取得を促す
await AdsConsent. requestInfoUpdate ();
const info = await AdsConsent. getConsentInfo ();
if (
info.isConsentFormAvailable &&
info.status === AdsConsentStatus. REQUIRED
) {
// 同意フォームを再表示
await AdsConsent. loadAndShowConsentFormIfRequired ();
}
Alert. alert (
'同意設定を更新しました' ,
'アプリを再起動すると新しい設定が完全に反映されます。' ,
);
} catch (error) {
Alert. alert (
'エラー' ,
'同意設定の更新に失敗しました。ネットワーク接続をご確認の上、もう一度お試しください。' ,
);
} finally {
setIsLoading ( false );
}
};
return (
< View style = {{ padding : 16 }} >
< Text style = {{ fontSize : 16 , fontWeight : '600' }} > プライバシー設定 </ Text >
< Text style = {{ marginTop : 8 , color : '#666' }} >
広告のパーソナライズに関する同意を変更できます。
</ Text >
< Button
title = {isLoading ? '処理中...' : '同意設定を変更する' }
onPress = {handleRevokeConsent}
disabled = {isLoading}
/>
</ View >
);
}
この画面は設定タブの「プライバシー」セクションなどに配置します。「同意設定を変更する」というボタン名にしているのは、「撤回」という言葉がネガティブに響くためです。ユーザーが心理的に押しやすい文言にすることで、UX を損なわずに法的要件を満たせます。
本番でよく詰まる落とし穴 6 選
実際に Rork アプリで UMP を実装していて、私や他の開発者が詰まったパターンを挙げます。どれも一度やらかすと修正に数時間〜数日かかる類いの問題なので、事前に頭に入れておく価値があります。
1. showIfRequired を呼ばずに AdMob を初期化してしまう
最も多い間違いです。requestInfoUpdate を呼んだだけで満足して、フォーム表示をスキップして AdMob を初期化すると、EEA ユーザーに対して同意を取っていない状態で広告リクエストが走ります。これが AdMob Publisher Policy 違反の典型パターンです。
2. ATT ダイアログを先に表示してしまう
iOS で request(PERMISSIONS.IOS.APP_TRACKING_TRANSPARENCY) を UMP より先に呼ぶと、ユーザーが混乱します。「許可」を押した後に UMP で「パーソナライズしない」を選ばれると、技術的には ATT は許可済みでも広告は非パーソナライズ配信になり、ユーザー体験としても矛盾します。必ず UMP → ATT の順番で。
3. Android 実機で同意ダイアログを確認しようとして出てこない
Android では AdsConsentDebugGeography.EEA を testDeviceIdentifiers と組み合わせないと、デバッグモードでも EEA 判定にならず同意フォームが出ません。テストデバイス ID は AdMob のログに出力されるので、最初の起動時にログを確認して設定ファイルに反映します。
4. 同意撤回 UI を作り忘れて App Store 審査でリジェクト
2024年以降、App Store Review Guidelines 5.1.1 に「ユーザーは同意を撤回できる」という項目が明文化されました。Play Store も同様です。撤回導線が設定画面にないと、審査でリジェクトされる可能性があります。
5. 非 EEA ユーザーに UMP を出してしまう
デバッグ時に debugGeography: AdsConsentDebugGeography.EEA を設定したまま本番リリースしてしまうと、非 EEA ユーザーにも UMP フォームが表示されます。これは UX 上の致命傷です(同意率が下がる → パーソナライズ広告が減る → CPM が落ちる)。__DEV__ で分岐するのを忘れずに。
6. 初回セッションだけ NPA モードになってしまう
UMP の同意取得が非同期なのに、AdMob の初期化を同期的に呼んでしまうと、初回起動時だけ「同意取得前の広告リクエスト」が走り、そのセッション中は NPA モードになります。必ず await initializeConsent() → await mobileAds().initialize() の順序で呼ぶこと。これだけで初回セッションの CPM が 20〜30% 改善することがあります。
AdMob Publisher Policy 違反から復旧する手順
もし既に AdMob から警告メールが来ている場合の復旧手順も書いておきます。私もこの手順で収益を回復させたことがあります。
まず上記の UMP 実装を入れた新バージョンをリリースする(TestFlight で動作確認してから本番反映)
AdMob 管理画面の「ポリシーセンター」で警告を受けたアプリを開き、「修正済みとしてマーク」を押す
48時間以内に Google 側の自動スキャンが走り、問題が解消されていれば警告が解除される
警告解除後、EEA からの広告配信が徐々に元に戻る(通常1週間以内)
収益が戻るまでには「新バージョン配布 → ユーザーのアップデート反映」のタイムラグがあるので、強制アップデートや更新促進のバナーを出すのも有効です。
テスト戦略 — 地域と状態のパターンを網羅する
UMP の動作確認は、大きく分けて以下の4パターンをテストすべきです。
EEA ユーザー・初回起動 : デバッグモードで EEA に設定 → 同意フォームが出る → 「許可」で canRequestAds: true になる
EEA ユーザー・同意拒否 : 同じくフォームを出し、「許可しない」で canRequestAds: false になる
非 EEA ユーザー : NOT_EEA に設定 → フォームは出ず即 canRequestAds: true
ネットワーク切断時 : 機内モードで起動 → requestInfoUpdate が失敗 → フォールバック動作
デバッグ用にビルドメニューから「同意状態をリセット」ボタンを出しておくと、何度もテストするときに便利です。本番ビルドでは __DEV__ ガードで隠します。
// デバッグビルドのみに表示
{__DEV__ && (
< Button
title = "[DEBUG] Reset Consent"
onPress = { async () => {
await AdsConsent . reset ();
Alert . alert ( 'Reset' , 'Consent state cleared. Restart the app.' );
}}
/>
)}
Rork の自動生成コードを本番品質まで引き上げるには、React Native 側の API の全体像を理解しておくと判断が速くなります。
Google 認定 CMP と自前 UMP のどちらを選ぶか
AdMob 内蔵の UMP SDK は無料で使えて、Rork アプリに最も導入しやすい選択肢です。ただし、機能面ではやや限定的で、「IAB TCF v2.2 の完全準拠」「Facebook や他のアドネットワークとの同意情報の連携」「同意履歴の法的証跡の保存」といった要件が必要になる規模の大きなアプリでは、Google 認定 CMP(Consent Management Platform)への移行を検討したくなる場面が出てきます。
代表的な Google 認定 CMP には Didomi、OneTrust、Usercentrics、iubenda などがあり、いずれも React Native 向けの SDK を提供しています。月額料金は MAU(月間アクティブユーザー)ベースで変動し、10 万 MAU 規模のアプリだと月額 2〜5 万円前後になる印象です。個人開発レベルでは AdMob 内蔵 UMP で十分ですが、以下のいずれかに該当するなら有料 CMP の検討価値があります。
AdMob 以外に Meta Audience Network や Unity Ads など複数のアドネットワークを統合している
広告以外にも Mixpanel や Amplitude などの分析ツールを使っていて、それぞれに同意情報を伝える必要がある
EEA 以外の地域法(米カリフォルニア州の CCPA、ブラジルの LGPD など)にも対応したい
GDPR の「同意履歴の証跡」としてサーバー側にログを残す必要がある
Rork アプリで AdMob のみを使っているうちは、内蔵 UMP で完結させるのが運用コストとのバランスが良いです。マネタイズ手段が増えてから、必要なタイミングで CMP に乗り換える、というのが個人開発者にとって現実的な判断だと思います。
EEA 以外で注意すべき規制地域 — CCPA・LGPD・PIPL
GDPR にばかり目が行きがちですが、近年は EEA 以外でも同種のプライバシー規制が増えています。Rork アプリを本格的にグローバル展開するなら、最低限以下の地域の規制も視野に入れる必要があります。
米カリフォルニア州(CCPA/CPRA) : 「Do Not Sell My Personal Information」リンクの設置義務。UMP は対応していないので、独自の Opt-Out 導線が必要
ブラジル(LGPD) : GDPR に近い内容だが、同意取得のタイミングと文言が一部異なる
中国(PIPL) : 個人情報の国外移転に事前同意が必要。Firebase など国外サーバーを使う場合は要注意
日本(改正個人情報保護法) : 2022年改正以降、Cookie 同様の識別子は「個人関連情報」として扱う必要あり
実装の観点では、まず AdMob 内蔵 UMP で EEA をカバーしつつ、CCPA については react-native-google-mobile-ads の設定で ccpaOptOut: true を渡す、日本向けには利用規約と同意チェックボックスを付ける、という段階的な対応で十分なケースが多いです。全規制に一度に対応しようとすると開発が止まるので、アプリのユーザー分布を見ながら影響の大きい地域から順に手を入れるのが現実的だと思います。
収益への影響をモニタリングする
UMP を実装したあと、「収益が本当に回復したか」を継続的に見るためのダッシュボードを作っておくと、次のトラブル時の発見が早くなります。AdMob 管理画面の「レポート」で以下の指標を週次で見る習慣をつけるのがおすすめです。
国別 eCPM : EEA 圏だけ極端に低い場合は UMP の同意率が低い(= フォームの文言が悪い、ボタンの配置が悪いなど)
広告リクエスト数 vs マッチ率 : マッチ率が 80% を大きく下回っていたら NPA モードに固定されている可能性
広告フォーマット別の収益 : リワード広告など NPA でも単価が落ちにくいフォーマットの割合
同意率は UMP のフォーム文言やボタンの配置で 10〜20% 変わることがあります。私は「パーソナライズ広告にご協力いただくと、より関連性の高い広告が表示されます」のように、ユーザーにとってのメリットを一言添えるだけで、同意率が上がった経験があります。押し付けがましくない範囲で工夫する価値はあります。
全体を振り返って — 今日着手すべき最初の一歩
UMP と ATT の実装は、一度きちんと組み込んでしまえばその後ほぼメンテナンスが不要な領域です。逆に言えば、放置したままグローバル展開すると、ある日突然 AdMob から警告が来て数日間収益がゼロになる、というリスクが常につきまといます。
まずやるべきは、自分のアプリが今 EEA ユーザーに対して UMP フォームを表示しているかを確認することです。TestFlight や Android 実機で AdsConsentDebugGeography.EEA を設定して起動し、フォームが出なければ即実装タスクとして優先度を上げるのがおすすめです。実装自体はこの記事のコードをそのままコピーして調整すれば、数時間で本番に出せるレベルにまで仕上がります。
関連する実装パターンについては、Rork の ATT 許諾率を最大化する AdMob 収益化戦略 と Rork アプリのプライバシー・データ保護ガイド も併せて読むと、iOS と Android の両方で完結した知識が身につきます。