表示回数は変わらないのに、収益だけが落ちた夜
AdMob の管理画面を開いたまま、深夜に手が止まったことがあります。表示回数は前の月とほとんど同じなのに、収益だけが目に見えて下がっておりました。
広告の出し方は何も変えていません。疑うべきは配置だと思い込んで、バナーの位置を動かしたり、インタースティシャルの出口を替えたりして、二晩を溶かしました。
原因は広告の外側にありました。iOS の許諾ダイアログを出すより先に、広告 SDK を初期化していたのです。順番が入れ替わっただけで、単価の高い配信面が届かなくなっていました。
広告の単価は配置ではなく、配置より手前の初期化と同意の順番で決まっています。 この一文を書き留めてから、私の見方は変わりました。以降、収益の話をするときは、まず広告の外側を疑うようにしております。
Rork のような生成ツールを使うと、実装そのものは短時間で終わります。だからこそ、時間の使いどころは「広告を貼ること」ではなく「広告が正しく届く状態を作ること」へ移ります。時間の使いどころが移った先を、その順番で書き残します。
Rork と AdMob の相性が良い理由は三つあります。1本あたりの実装が数日で終わること、企画を試す回数を増やせること、そして React Native ベースなので react-native-google-mobile-ads がそのまま入ることです。
AdMob が成り立つジャンルと、成り立たないジャンル
すべてのアプリが広告で成り立つわけではありません。収益モデルの向き不向きは、コードを書く前の企画段階でほぼ決まってしまいます。
広告と相性の良いジャンル
ユーティリティ系 — 計算機、単位変換、QR コードリーダー、ライト、コンパス。特定の用途で繰り返し開かれるため、1回あたりの単価が低くても起動回数が積み上がります。
待ち時間を埋めるもの — シンプルなパズル、暇つぶしゲーム、占い、診断。1日に何度も開かれる性質があり、リワード広告との相性がとりわけ良いジャンルです。
眺めて過ごすコンテンツ — 壁紙、環境音、瞑想ガイド、引用集。1セッションが長く、同じ画面を繰り返し見るため、バナーの収益が安定します。私自身がこの領域でアプリを運用しており、日々の運用感覚もここから来ています。
軽い学習系 — 単語帳、暗記カード、フレーズ集。定着率が高く、月をまたいで収益が積み上がっていきます。
広告と相性の悪いジャンル
毎日長く使う実用アプリ — タスク管理、本格的なノート、家計簿。1日10分以上向き合うアプリでは、広告そのものが摩擦になります。課金プランへ逃げるか、広告のない競合へ移るかのどちらかで、結局は定着率を削ります。サブスクリプションのほうが素直です。
利用者の母数が小さい専門ツール — 特定業界向けの管理ツール、特殊な計算アプリ。母数が小さすぎて、広告の掛け算が成立しません。買い切りの有料アプリにするほうが筋が通ります。
やり取りが主役のアプリ — チャット、コミュニティ。会話の途中に広告が挟まる体験は、機能の良し悪し以前に嫌われます。
判断は Rork で企画を立てる段階で済ませます。「これは広告で成り立つジャンルか」を最初に問い、成り立たないなら収益モデルを変えるか、企画そのものを替える——そのほうが、公開後に配置をいじり続けるより、はるかに早く答えに辿り着きます。収益モデルの選び方そのものはRork アプリの収益モデル比較 — 広告・サブスク・買い切りを実運用の数字で選ぶ に整理しております。
月10万円ラインに乗るアプリが満たしている条件
広告で月10万円へ近づいたアプリには、共通して満たしている条件があります。四つに絞って書きます。
条件1: DAU が 3,000 人を超えていること
eCPM(広告 1,000 回表示あたりの収益)はジャンルと国で大きく動きますが、日本の表示広告で 200〜500 円あたりが目安です。1 ユーザーが 1 日に 3〜5 回広告を見ると置くと、月 10 万円のラインには 3,000〜5,000 人ほどの日次アクティブユーザーが必要になります。
インストール総数で 5 万〜10 万件、月次アクティブで 1.5 万〜3 万人が、その手前の目安です。
条件2: D7 リテンションが 20% を超えていること
インストールから 1 週間後にも開いてくれる方の割合です。ここが 20% を下回るアプリは、新規流入を買い続けないと数字を保てず、広告収益を広告費が食べてしまいます。
改善の投下先は、ほぼ初回セッションに集中します。30 秒以内に最初の価値を渡すこと、そして 2 回目の起動で目新しさを一つ用意すること。この二点だけで、数字はよく動きます。
条件3: 英語を含む多言語で出していること
日本市場は単価が世界の中位です。米国や西欧の eCPM は日本の 2〜3 倍あるため、英語対応の有無がそのまま収益の桁に効きます。
Rork で作るなら、最初から日英セットで組んでおくほうが楽です。あとから多言語化するより、遥かに手数が少なく済みます。
条件4: トラッキング許諾の設計ができていること
iOS 14 以降、ATT(App Tracking Transparency)で「許可しない」を選ばれると単価が落ちます。ここは説明文と出すタイミングの設計次第で、許諾率がはっきり変わる領域です。次の節で実装に踏み込みます。
ATT の許諾を取り終えてから初期化する
冒頭に書いた失敗の中身です。Google Mobile Ads SDK は、ATT の結果を受け取る前に初期化してしまうと、その起動では許諾に沿った配信設定を取り込めません。順番を守るだけで、単価の落ち込みが止まります。
まず app.json に、広告 ID と許諾ダイアログの説明文を書きます。
{
"expo" : {
"plugins" : [
[
"react-native-google-mobile-ads" ,
{
"androidAppId" : "ca-app-pub-XXXXXXXXXX~YYYYYYYYYY" ,
"iosAppId" : "ca-app-pub-XXXXXXXXXX~YYYYYYYYYY"
}
],
[
"expo-tracking-transparency" ,
{
"userTrackingPermission" : "広告をあなたに合った内容にするために使用します。許可しない場合も、アプリの機能はすべてご利用いただけます。"
}
]
]
}
}
説明文は、許諾率をいちばん左右する一文です。「トラッキングを許可しますか」とだけ書くより、何に使うのかと、断っても損をしないことの二つを短く添えるほうが通ります。
初期化はこの順番で書きます。
import { Platform } from "react-native" ;
import mobileAds, { MaxAdContentRating } from "react-native-google-mobile-ads" ;
import {
getTrackingPermissionsAsync,
requestTrackingPermissionsAsync,
PermissionStatus,
} from "expo-tracking-transparency" ;
export async function initializeAds () {
// iOS では ATT の結果が確定してから初期化する
if (Platform. OS === "ios" ) {
const { status } = await getTrackingPermissionsAsync ();
if (status === PermissionStatus. UNDETERMINED ) {
await requestTrackingPermissionsAsync ();
}
}
await mobileAds (). setRequestConfiguration ({
maxAdContentRating: MaxAdContentRating. T ,
tagForChildDirectedTreatment: false ,
tagForUnderAgeOfConsent: false ,
});
return mobileAds (). initialize ();
}
なぜこう書くのかを補います。getTrackingPermissionsAsync を先に挟んでいるのは、2 回目以降の起動で不要なダイアログを出さないためです。ATT は一度答えられると再表示できないため、UNDETERMINED のときだけ要求します。
そして initialize() を待ってから、最初の広告リクエストを投げます。スプラッシュの裏で初期化を走らせ、ホーム画面の描画完了後に読み込みを始める順番が、体感としてもいちばん安定します。
ダイアログを出す位置にも一つだけ線引きがあります。起動直後の 1 枚目には出しません。 アプリが何をするものか分からないうちに許諾を求めても、断られるだけです。最初の価値を渡し終えた直後——壁紙を 1 枚保存できた、診断結果を 1 回見られた——そのタイミングまで待つほうが、許諾率は上がります。
広告配置のパターンと、頻度を時間で制御する実装
配置そのものは、素直に三つのパターンへ収束します。
パターン1: 起動時インタースティシャル + 常設バナー
いちばん簡単な形です。起動時に 1 回だけ全面広告、メイン画面に常時バナー。収益は中程度ですが、実装が軽いので 1 本目に向いています。
import { BannerAd, BannerAdSize } from "react-native-google-mobile-ads" ;
function HomeScreen () {
return (
< View style = { { flex: 1 } } >
< ContentArea />
< BannerAd
unitId = "ca-app-pub-XXXX/YYYY"
size = { BannerAdSize. ANCHORED_ADAPTIVE_BANNER }
/>
</ View >
);
}
ANCHORED_ADAPTIVE_BANNER は端末の横幅に合わせた高さを自動で選びます。固定サイズより余白が破綻しにくく、レイアウトの調整に費やす時間が減りました。
パターン2: 行動の完了時にインタースティシャル + リワードでの解放
ゲームやパズルに向く形です。ステージのクリア時に全面広告、追加コンテンツの解放にリワード広告。パターン1 と比べて、1 ユーザーあたりの収益が 1.5〜2 倍になります。
import { RewardedAd, RewardedAdEventType } from "react-native-google-mobile-ads" ;
const rewarded = RewardedAd. createForAdRequest ( "ca-app-pub-XXXX/WWWW" );
rewarded. addAdEventListener (RewardedAdEventType. EARNED_REWARD , () => {
unlockPremiumContent ();
});
リワード広告は、視聴するかどうかを読者側が選べます。単価がバナーの数倍あり、体験を削りにくい——この二つを同時に満たす枠は他にありません。
パターン3: 一覧の中に差し込むネイティブ広告
ニュースやキュレーションのように、縦に読み進める画面に向く形です。5〜7 件ごとに 1 枠を差し込みます。クリック率が高く、他のパターンの 2〜3 倍になることもあります。
頻度は「回数」ではなく「時間」で持つ
私が最初につまずいたのはここでした。全面広告を「3 回に 1 回」と回数で数えていたところ、画面を素早く往復する使い方をされると、数十秒のあいだに 2 枚出てしまっていたのです。回数は、体感の間隔を保証してくれません。
いまはクールダウンの時間で持たせています。
import AsyncStorage from "@react-native-async-storage/async-storage" ;
const COOLDOWN_MS = 4 * 60 * 1000 ; // 4 分
const KEY = "ads:lastInterstitialAt" ;
export async function maybeShowInterstitial () {
const saved = await AsyncStorage. getItem ( KEY );
const lastShownAt = saved ? Number (saved) : 0 ;
const now = Date. now ();
if (now - lastShownAt < COOLDOWN_MS ) return false ;
if ( ! interstitial.loaded) return false ;
interstitial. show ();
await AsyncStorage. setItem ( KEY , String (now));
interstitial. load (); // 次回ぶんを先読み
return true ;
}
AsyncStorage に落としているのは、アプリを閉じて開き直す使い方で間隔がリセットされないようにするためです。メモリ上の変数だけで持つと、起動のたびに全面広告が出る作りになってしまいます。
interstitial.loaded が false のときは何も出さずに握りつぶします。読み込みを待たせてまで広告を見せる価値は、少なくとも私の運用範囲ではありませんでした。
eCPM ではなく ARPDAU で判断する
配置を変えたときの良し悪しを、eCPM だけで見ていた時期があります。単価は上がったのに、月末の合計は下がっている——そんな月が続いて、指標を替えました。
いま見ているのは ARPDAU(1 日あたり 1 アクティブユーザーの収益)です。収益 ÷ DAU で出せます。広告を増やせば eCPM や表示回数は動きますが、同時に離脱も増えるため、両方を織り込んだ数字でないと判断を誤ります。
ARPDAU と D7 リテンションの二つを並べて、両方が下がらない範囲でだけ広告を足します。これが、いまの判断の仕方です。複数ネットワークで入札を競わせる段階の話は、Rork アプリで AdMob Bidding を本番投入した実装メモ にまとめております。
app-ads.txt と ASO — 収益の手前にある二つの土台
広告のコードが正しくても、その手前で漏れている箇所が二つあります。
app-ads.txt を置く
自分の広告枠を誰が売ってよいかを宣言するファイルです。ストアの掲載情報に書いたデベロッパー Web サイトのルートに app-ads.txt を置き、AdMob の管理画面が示す行をそのまま書きます。
google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0
置いていないと、認証済みの売り手からしか買わない設定の広告主が入札に参加できません。設置してから、単価の上振れが出るようになりました。ストアのサイト URL とファイルを置いたドメインが一致していないと読まれないため、ここだけは公開後に一度確かめてください。
ストアで見つけてもらう
作っただけでは収益は立ちません。iOS ならキーワードフィールドの 100 文字、Android なら説明文の中の語彙が、そのまま検索流入を決めます。
一般的すぎる語(「ゲーム」「ツール」)は競合が強すぎて届きません。狭くて検索のある語を選びます。日本語での組み立て方はRorkで作ったアプリのApp Storeキーワードフィールドを100文字で最大化する設計術 に詳しく書きました。
スクリーンショットは、ストアにおける唯一の広告枠です。私は 5 枚をこう使い分けています。
評価の依頼は、満足している可能性が高い瞬間に絞ります。
import * as StoreReview from "expo-store-review" ;
async function askForReviewIfAppropriate ( sessionCount : number , positiveSignal : boolean ) {
if (sessionCount !== 5 || ! positiveSignal) return ;
if ( ! ( await StoreReview. isAvailableAsync ())) return ;
if ( ! StoreReview. hasAction ()) return ;
await StoreReview. requestReview ();
}
isAvailableAsync と hasAction を先に見ているのは、依頼が表示されない環境で無駄に呼ばないためです。Apple 側の上限は 1 年に 3 回までですから、出せる回数は本当に限られています。
複数アプリでポートフォリオを組む
1 本で月 10 万円を狙うより、5〜10 本の合計で同じ額に届かせるほうが、個人開発では現実的です。理由は三つあります。
リスクが割れること。 1 本への依存は、ポリシー変更、審査、競合の登場のどれか一つで簡単に崩れます。分散していれば、1〜2 本が落ちても全体は大きく動きません。
学習が積み上がること。 10 本作る過程で、どのジャンル・どの配置・どの語彙が効くかが手触りとして残ります。1 本だけでは、当たったのが偶然なのか再現できるものなのかを判断できません。
Rork なら本数が現実的になること。 1 本あたりの実装が 1〜2 週間で収まるため、週末の集中作業で月に 1〜2 本は形になります。半年で 6〜12 本のポートフォリオが組めます。
私自身は壁紙や環境音のような、眺めて過ごす種類のアプリを複数運用しております。どれか 1 本が屋台骨というより、合計で足元を支えてくれている、という感覚に近いのです。
広告以外の収益と組み合わせる
広告だけでは天井が来ます。上限を押し上げるのは、たいてい広告以外の側です。
広告除去の買い切り。 「広告なし 480 円」のような一度きりの購入枠を足します。広告が我慢できない方が自分で選べる出口を用意しておく、という考え方です。購入率は数パーセントですが、母数が増えれば効いてきます。
追加コンテンツのサブスクリプション。 眺めて過ごす種類のアプリなら、限定の壁紙や音源を月額で提供できます。手数料は差し引かれますが、月をまたいで積み上がる収益は性質が違います。
文脈に合う紹介。 ユーティリティ系で、その場面に本当に必要な物だけを紹介する形です。割り込みではなく提案になるため、体験を削りません。数を打つと途端に嫌われるので、私は 1 画面に 1 つまでと決めています。
90日の進め方と、やらないと決めたこと
1〜14 日目 — 相性の良いジャンルから企画を三つ出し、それぞれ競合を 3〜5 本ダウンロードして触ります。違いを作れる一つを選び、Rork で初版を組みます。
15〜30 日目 — AdMob の統合(バナー・インタースティシャル・リワード)と ATT の順番を実装。日英の 2 言語、スクリーンショット、ストア説明文まで仕上げて、iOS と Android の両方へ提出します。app-ads.txt もここで置きます。
31〜45 日目 — キーワードの調整、レビューの収集、初期の DAU づくり。並行して 2 本目の企画と実装を始めます。
46〜70 日目 — 1 本目の ARPDAU と D7 を読み、2 本目へ反映。3 本目の実装を開始し、1 本目には大きめの更新を当てます。
71〜90 日目 — 3 本を運用し、合計 DAU を 1,000〜2,000 人へ。ここで初めて、4 本目以降を続けられる体制になっているかを確かめます。
そのうえで、やらないと決めたことが三つあります。
広告を詰め込まないこと。 起動時、画面遷移ごと、完了時、表示前——すべてに出すアプリは、必ず外されます。頻度を落とした月の収益は確かに減りました。それでも半年後には、落とす前より高い水準に戻っていたのです。数字が戻るまでの数か月を耐えられるかどうかだけが、分かれ目でした。
短期の単価を追わないこと。 単価が高いからという理由だけで、必要のない場面にリワード広告を挟むと、「広告を見ないと使えないアプリ」だと受け取られます。そう感じられた瞬間から、評価は戻りません。
一発逆転を狙わないこと。 次は当たると信じて大きなアプリに長く投資するのが、いちばん危ういやり方だと感じています。どれが当たるかは、経験を積んでも読み切れません。小さく出して、データを見て、直します。遠回りに見えて、これが最短でした。
次の一歩
まず、いま自分が毎日開いているアプリのうち、広告で運営されているものを 1 本選んでください。どの瞬間の広告が許せて、どの瞬間が煩わしかったかを書き出す——それだけで、自分が作るアプリの設計指針になります。
そのうえで、ATT の許諾を取り終えてから初期化する順番だけは、1 本目から守っていただければと思います。あとから直せる箇所はいくらでもありますが、この順番は最初に入れておくのがいちばん安く済みます。
ここまでお読みいただき、ありがとうございました。