リリースの翌週、朝いちばんに App Store Connect の「売上とトレンド」を開いて、収益の欄にゼロが並んでいるのを眺めておりました。ダウンロードは毎日ついています。星も付き始めています。けれど、お金の欄だけが動かない——収益化の問いは、いつもこの画面から始まるのだと思います。
個人開発で壁紙アプリやヒーリング音源アプリを長く運用してきて、いまも変わらず感じているのは、「どう作るか」と「どう収益化するか」は別の問題だということです。Rork のおかげで前者にかかる時間はかつてないほど短くなりましたが、後者は一日も短くなっておりません。
最初にお伝えしたいのは、ここに並べるのは誰かの売上ではなく、皆さまが自分のアプリの数字を置いて判断できる物差しだということです。広告・アプリ内課金・サブスクリプションの三つを、向き不向き、実装の急所、そして自分で試算する式の三点で整理してまいります。
三つのモデルを、向き不向きから理解する
ひとつめは「広告モデル」です。Google AdMob・Meta Audience Network・Unity Ads などの広告ネットワークから配信される広告を表示し、表示回数やクリックで収益を得る形です。ユーザー一人あたりの月収益は小さく、私の観測範囲では日本向けのカジュアルアプリで数円から数十円のあいだに収まることが多いのです。
このモデルが向いているのは、ユーザーが「無料で使えて当たり前」と感じるカジュアルアプリです。壁紙・天気・タイマー・ライフスタイル系がその典型で、私が長く運用しているアプリもほとんどがこの括りに入ります。
ふたつめは「アプリ内課金(IAP)モデル」です。「広告を消す」「機能を解放する」「コンテンツを追加する」といった価値をアプリ内で買っていただく形で、課金してくださった一人あたりの収益は広告よりはるかに高くなります。ただし、課金に至る方の割合は数パーセントに留まるのが普通です。
向いているのは、「この機能は有料でも欲しい」と一文で説明できる差別化要素を持つアプリです。写真編集・PDF 編集・専門ツール系、そしてゲームの追加コンテンツがここに入ります。
みっつめは「サブスクリプションモデル」です。月額・年額で継続して課金していただく形で、一人あたりの収益は三つのなかでいちばん高くなりやすく、安定収益にもつながります。一方で、継続して価値を提供できる構造が前提になり、解約率の管理が運営の核心になります。
向いているのは、「使い続けることで価値が積み上がる」アプリです。学習系・健康管理・家計簿・コンテンツ配信系が典型で、AI を組み込んだアプリはその性質上、サブスクと相性が良いのだと感じています。
どのモデルを選ぶかの判断軸
「とりあえず全部入れる」は、最初に陥りやすい罠です。広告と IAP とサブスクを全部入れたアプリは UI が複雑になり、ユーザーは何にお金を払えばいいのか分からず、結果として何も買わないという現象が起きます。私自身、初期のアプリで三つを同居させ、どれも中途半端な数字で終わった経験があります。
私が個人開発でモデルを決めるときに使っている判断軸は四つです。
ひとつめは「見込める DAU の規模」です。多くの人が毎日開くアプリなら広告モデルが第一候補になり、数千人規模に留まるなら IAP かサブスクが必須になります。これは一人あたり収益の差から逆算した相場感で、あとで示す試算コードに自分の数字を入れていただくと、この差がはっきり見えてくるはずです。
ふたつめは「差別化要素の強さ」です。「Pro 版でこれが使える」と明確に説明できる機能があるなら IAP かサブスクへ進み、差別化が弱いなら広告モデルで量を取る戦いになります。
みっつめは「継続利用の頻度」です。毎日使うアプリならサブスクが成立しやすく、週に一度も開かれないアプリでは、サブスクは解約の理由を毎月作っているようなものになってしまいます。
よっつめは「ターゲット層の課金耐性」です。若年層やカジュアルなユーザー向けは広告中心が、ビジネスパーソンや専門職向けはサブスクや高単価の IAP が成立しやすいと感じています。
私の手元では、壁紙アプリのように差別化の弱いものは広告中心、写真加工のように機能で選ばれるものは IAP 中心、業務で毎日開いていただくツールはサブスク中心、というかたちで分けております。モデルは足すより、まず一つを成立させることです。 この一文を、いまは判断の起点にしています。
自分の数字で試算する — 三モデルの月次収益を並べる物差し
ここが、この記事でいちばんお持ち帰りいただきたい部分です。他人の売上を眺めても自分のアプリの判断はできませんが、同じ形の式に自分の仮定値を入れて三つを並べると、どのレバーが収益を動かすのかが手触りとして分かってきます。
// 三モデルの月次収益を、同じ粒度の入力から見積もる最小の物差し
type AdsInput = { dau : number ; impressionsPerDau : number ; ecpmJpy : number };
type IapInput = { mau : number ; payerRate : number ; arppuJpy : number };
type SubInput = {
trialStartsPerMonth : number ;
trialToPaid : number ; // トライアルから本課金への転換率(0〜1)
monthlyPriceJpy : number ;
monthlyChurn : number ; // 課金者の月次解約率(0〜1)
};
export function estimateAds ({ dau , impressionsPerDau , ecpmJpy } : AdsInput ) {
// eCPM は 1,000 表示あたりの収益なので 1,000 で割る
return Math. round ((dau * impressionsPerDau * 30 * ecpmJpy) / 1000 );
}
export function estimateIap ({ mau , payerRate , arppuJpy } : IapInput ) {
return Math. round (mau * payerRate * arppuJpy);
}
export function estimateSubSteadyState ({
trialStartsPerMonth ,
trialToPaid ,
monthlyPriceJpy ,
monthlyChurn ,
} : SubInput ) {
// 毎月同じ数のトライアルが入り続けたときに落ち着く課金者数 = 新規課金者 ÷ 月次解約率
const newPayers = trialStartsPerMonth * trialToPaid;
const steadyPayers = monthlyChurn > 0 ? newPayers / monthlyChurn : Infinity ;
return {
steadyPayers: Math. round (steadyPayers),
monthlyRevenueJpy: Math. round (steadyPayers * monthlyPriceJpy),
// 定常状態に近づくまでの目安(月)。解約率の逆数がそのまま時間軸になる
monthsToSettle: monthlyChurn > 0 ? Math. round ( 1 / monthlyChurn) : Infinity ,
};
}
仮定値を入れて並べてみます。数字はどれも私が置いた仮定で、皆さまのアプリの値に置き換えていただくためのものです。
この表を作って気づいたのは、サブスクの見積りだけが「時間」を含んでいることでした。広告と IAP は今月の数字が今月の収益ですが、サブスクは解約率の逆数だけ待たないと定常状態に届かないのです。初月の売上でサブスクを諦めてしまう判断が、いかに早すぎるかもここから読めます。
逆に、広告の行を見ると、表示回数を増やしても DAU が伸びなければ天井が近いことが分かります。私は一時期、一人あたりの表示回数を増やす方向へ流されて評価を落としたことがあり、この式を眺め直してから「増やすなら DAU、削るなら表示」と決めるようになりました。
広告モデルの実装ポイント
Rork で広告を組み込む場合、AdMob がいちばん導入しやすく、レポートも細かく見られます。実装で押さえておきたい点を書き残します。
// React Native + Rork での AdMob バナー実装例
import { View } from "react-native" ;
import { BannerAd, BannerAdSize, TestIds } from "react-native-google-mobile-ads" ;
function AppScreen () {
// 開発中はテスト ID を使う。本番 ID で自分の端末をタップし続けると無効トラフィック扱いになる
const adUnitId = __DEV__
? TestIds. BANNER
: "ca-app-pub-XXXXXXXXXXXXXXXX/YYYYYYYYYY" ;
return (
< View style = {{ flex : 1 }} >
< YourMainContent />
< BannerAd
unitId = {adUnitId}
size = {BannerAdSize.ANCHORED_ADAPTIVE_BANNER}
requestOptions = {{
requestNonPersonalizedAdsOnly : false ,
}}
/>
</ View >
);
}
実装上の急所は三つあります。
ひとつめは「広告フォーマットの使い分け」です。画面下部に常時出るバナー、画面切り替え時に全面表示されるインタースティシャル、ユーザーが自分の意思で見て報酬を得るリワードの三種類を、アプリの性質に合わせて配置します。私のアプリでは「リワード広告で機能を一時解放する」形が、収益と体験のあいだでいちばん折り合いが良いと感じています。
ふたつめは「初回起動からしばらくは広告を出さない」ことです。起動直後に広告を出すと体験が大きく損なわれ、レビューの星に直結します。最低でも 30 秒から 1 分の猶予を置くようにしています。
みっつめは「インタースティシャルの表示頻度を制御する」ことです。画面遷移のたびに毎回出すと、ユーザーは黙って去っていきます。「3 回に 1 回」「前回の表示から 5 分以上」のような制御ロジックを、広告を呼ぶ一か所に集約して持たせるのが扱いやすいです。
AdMob メディエーションで eCPM を底上げする実務
広告モデルを選んだあと、収益を左右するのは「どのネットワークから広告を取るか」です。AdMob 単独でも動きますが、複数の広告ネットワークを束ねるメディエーションを組むと、同じ表示回数でも eCPM(1,000 表示あたり収益)が変わってきます。
私は長らく AdMob をメディエーションのハブに据え、Meta Audience Network・Unity Ads・AppLovin・Pangle を束ねる構成で運用してきました。単独運用からメディエーションへ切り替えた壁紙アプリでは、リワード面の eCPM が概ね 1.4 倍に上がりました。理由は単純で、同じ 1 インプレッションを複数ネットワークに競わせると、より高い単価を出したネットワークが落札するからです。
ここで判断が要るのが「ウォーターフォール」と「ビディング(入札)」の使い分けです。ウォーターフォールは各ネットワークに固定の優先順位と単価フロアを設定し、上から順に在庫を問い合わせる方式で、ビディングは全ネットワークに同時入札させて最高値を採る方式です。私の実感では、主要面はビディングに寄せて取りこぼしを減らし、単価の読める補助ネットワークだけウォーターフォールに残す折衷が扱いやすいのです。全面ウォーターフォールは設定こそ楽ですが、問い合わせのレイテンシが積み上がり、広告が出るまでの待ち時間が伸びていきます。
もうひとつ、収益の解像度を上げてくれるのが eCPM の「揺れ」を知ることです。私が観測してきた範囲では、日本が眠る深夜帯より北米が動いている時間帯のほうがリワード eCPM が高く出やすく、同じアプリでも配信国の構成で月次収益が二割ほど動きます。だからこそ、収益レポートは合計値だけでなく「国別 × フォーマット別 × 時間帯別」で見る癖をつけると、どの面を厚くすべきかが見えてきます。
数値はアプリのジャンルや時期で動きますので、絶対値より「フォーマット間の序列」を掴む材料として捉えていただければ幸いです。私自身、最初は eCPM の高いリワードばかり増やそうとして体験を崩しかけ、あとから「任意視聴だから増やしても嫌われにくい面」に絞り直した経緯があります。数字は増やす方向だけでなく、どこで止めるかを教えてくれるものでもありました。ネットワークごとの設定の細部は Rork アプリの AdMob 収益化戦略 に分けて書き残しております。
アプリ内課金(IAP)モデルの実装ポイント
IAP は React Native では react-native-iap、Apple と Google のストアでの商品設定、そしてサーバー側の検証が必要になります。Rork の場合、画面側の実装はかなり楽ですが、購入の「完了」をどこで宣言するかだけは自分で線を引く必要があります。
import { useEffect } from "react" ;
import { Platform, View, Text, TouchableOpacity } from "react-native" ;
import { useIAP } from "react-native-iap" ;
const productIds = [ "pro_unlock" , "premium_pack" , "feature_x" ];
function PurchaseScreen () {
const { connected , products , getProducts , requestPurchase , currentPurchase , finishTransaction } = useIAP ();
useEffect (() => {
if (connected) getProducts ({ skus: productIds });
}, [connected]);
// 購入結果はコールバックではなく currentPurchase の変化として届く
useEffect (() => {
if ( ! currentPurchase) return ;
( async () => {
// 1. まずサーバーへ送って検証する(クライアントの結果を信じない)
const ok = await verifyOnServer (currentPurchase);
// 2. 検証が通ってから finishTransaction。ここを先にすると検証前に消費されてしまう
if (ok) await finishTransaction ({ purchase: currentPurchase, isConsumable: false });
})();
}, [currentPurchase]);
const handlePurchase = async ( sku : string ) => {
try {
// v12 系は iOS と Android で引数の形が違う。導入したバージョンの型定義で必ず確認する
await requestPurchase (Platform. OS === "ios" ? { sku } : { skus: [sku] });
} catch (err) {
console. error ( "Purchase failed" , err);
}
};
return (
< View >
{ products . map (( p ) => (
< TouchableOpacity key = {p.productId} onPress = {() => handlePurchase (p.productId)} >
< Text >{p.title} - { p . localizedPrice } </ Text >
</ TouchableOpacity >
))}
</ View >
);
}
急所は三つあります。
ひとつめは「サーバー側で必ず検証する」ことです。クライアントだけで購入完了を信じると、改ざんされた結果を受け入れて不正に機能を解放してしまいます。Apple は従来の verifyReceipt が非推奨になっており、いまは App Store Server API でトランザクション ID から署名付きの取引情報を取る形が正です。Google は Play Developer API の purchases.products.get(買い切り)と purchases.subscriptionsv2.get(サブスク)で確認できます。検証が通るまで finishTransaction を呼ばない——この順番だけは崩さないようにしております。
ふたつめは「購入の復元(Restore Purchase)を必ず用意する」ことです。これがないと、機種変更のあとに「払ったのに使えない」という、いちばん胃の痛いレビューが届きます。Apple の審査でもたびたび指摘される項目です。
みっつめは「価格を国別に見直す」ことです。日本の 480 円が米国の 4.99 ドルと等価とは限りません。ストアの価格帳を眺め、各国の購買力に合わせた価格に寄せるだけで、私のアプリでは購入数の分布が目に見えて変わりました。倍率を約束することはできませんが、既定の一律換算のままにしておく理由もないのです。
サブスクリプションモデルの実装ポイント
サブスクは IAP の特殊形ですが、運用の複雑さは別物です。私が運用しているサブスクアプリで重いと感じている点を、三つに絞って書き残します。
ひとつめは「無料トライアル期間の設計」です。3 日・7 日・14 日のどれにするかで転換率が変わります。私の手元では、使い込みが要るツール系は 14 日、コンテンツ系は 7 日で落ち着きました。3 日は価値を感じる前に期限が来てしまい、転換前に解約されることが多かったのです。
import { Platform } from "react-native" ;
import { requestSubscription } from "react-native-iap" ;
const subscriptionId = "monthly_pro" ;
// Android はオファーごとの offerToken が必須。getSubscriptions() の結果から取り出す
const handleSubscribe = async ( offerToken ?: string ) => {
try {
await requestSubscription ({
sku: subscriptionId,
... (Platform. OS === "android" && offerToken
? { subscriptionOffers: [{ sku: subscriptionId, offerToken }] }
: {}),
// トライアル期間はストア側の商品設定で持つ。コード側は検証後に finishTransaction する方針を守る
andDangerouslyFinishTransactionAutomaticallyIOS: false ,
});
} catch (err) {
console. error ( "Subscription failed" , err);
}
};
ふたつめは「サブスク状態の管理」です。解約しても、いまの課金期間が終わるまで Pro 機能が使える状態を保つ必要があります。サーバー側で expiresAt を必ず記録し、expiresAt > now の判定だけで機能解放を制御するのが、いちばん事故が少ないのです。
みっつめは「解約直前の情報提示」です。「いまキャンセルすると使えなくなる機能」と「期間中は引き続き使える」ことを解約ボタンの手前で一度だけ伝えます。私のアプリでは、この一画面を足した月から解約の踏みとどまりが目に見えて増えました。脅すのではなく、失うものを正確に伝えるだけで十分だと感じています。
サブスク解約を「数字」で管理する — コホートで見る継続率
サブスクは入り口の獲得より、出口の解約をどう抑えるかが収益を決めます。ここで役立つのが、登録した月ごとに束ねてその後の継続率を追う「コホート」の見方です。
私が運用する業務向けアプリでは、トライアル開始から本課金への転換がおよそ三割、初月を越えた方の翌月継続がおよそ九割弱という水準で推移しています。ここで大事なのは、この二つの数字を分けて見ることです。転換率が低いならトライアル体験や価格に問題があり、初月後の継続が低いなら価値提供そのものに穴があるのだと読みます。混ぜて「解約率が高い」と嘆いても、打ち手が定まりません。
// 登録月ごとのコホートで N か月後の継続率を出す最小例
type Sub = { userId : string ; startedAt : Date ; canceledAt : Date | null };
function retentionByCohort ( subs : Sub [], monthsOut : number ) {
const cohorts : Record < string , { total : number ; retained : number }> = {};
for ( const s of subs) {
const key = `${ s . startedAt . getFullYear () }-${ s . startedAt . getMonth () + 1 }` ;
cohorts[key] ??= { total: 0 , retained: 0 };
cohorts[key].total ++ ;
const boundary = new Date (s.startedAt);
boundary. setMonth (boundary. getMonth () + monthsOut);
// monthsOut か月後もまだ解約していなければ継続とみなす
if ( ! s.canceledAt || s.canceledAt > boundary) cohorts[key].retained ++ ;
}
return Object. entries (cohorts). map (([ month , c ]) => ({
month,
retention: c.total ? Math. round ((c.retained / c.total) * 100 ) : 0 ,
}));
}
このコホート表を毎月眺めていると、価格改定や機能追加が「どの登録月から効いたか」が線として見えてきます。私は一度、トライアルを 7 日から 14 日に延ばした月を境に転換率が数ポイント上がったことがあり、それはコホートに並べて初めて確信できました。合計値の折れ線だけを見ていたら、季節変動と区別がつかなかったはずです。
解約の抑止は UI だけの仕事ではありません。継続率の低いコホートに共通する「使われていない機能」を見つけ、そこを作り直すほうが、引き止めダイアログを一つ足すよりも効きます。数字は責めるためでなく、次にどこへ手を入れるかを教えてくれる地図として使いたいところです。
法令面で必ず押さえること
国内でサブスクや IAP を提供する場合、実装より先に確認しておきたいことが三つあります。
「特定商取引法に基づく表記」を設置することです。屋号・所在地・連絡先・返金方針を明記したページが必要で、これがないとストア審査で止まることがあります。
「自動更新の明示」を行うことです。定期購入については、いつ・いくら・どう解約するかを申込みの最終確認画面で示すことが求められています。ストアの購入シートが多くを担ってくれますが、アプリ内の説明画面でも同じ内容を繰り返しておくと、返金の問い合わせが減るのを感じました。
「子ども向けアプリの広告制限」に注意することです。13 歳未満を対象にしたアプリは、AdMob でも表示できる広告が限られます。ストアの年齢区分と広告設定が食い違っていると、リジェクトや収益停止につながりかねません。
一つのモデルで成立させてから、次を足す
収益化でいちばん重い決定は、「自分のアプリにどのモデルが合うかを最初に決める」ことです。「全部入れる」は楽そうに見えて、実はいちばん難しい選択肢になります。判断軸で候補を絞り、試算コードに自分の仮定値を入れ、一つのモデルで成立させてから、必要に応じて次を重ねる——この順番が、私には現実的でした。
今日ひとつだけ手を動かすなら、先ほどの estimateAds estimateIap estimateSubSteadyState に、皆さまのアプリの DAU・課金率・解約率の見込みを入れて三つの数字を並べてみてください。どのレバーが自分のアプリを動かすのかが、他人の売上を眺めるより早く見えてくるはずです。
そのうえで月次の収益を一段引き上げる具体策へ進みたい方は、姉妹記事の 個人開発で月10万円を超えるアプリ収益化の実践戦略 で、Rork で作ったアプリで実際に試した収益チューニングの実装と運用フローを書き残しております。