●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●RORK — GPT-6.1 Sol (Sep 29) and Claude Sonnet 5.5 (Sep 28) are now in the model menu●RN 0.88 — The stable release was still unpublished on its Oct 12 target. rc.4 (Oct 7) is the newest; 0.87.1 is Latest●Q&A — Readers report expo-widgets showing a black widget in production builds only●WINDOWS — A walkthrough shows how to ship an iPhone app from Windows only, with no Mac●NEW — Three checks to run first when notifications stop arriving in Expo Go●EXPO — EAS Observe now records native crashes (Oct 7). On SDK 57, update to 57.0.21 or later●RORK — GPT-6.1 Sol (Sep 29) and Claude Sonnet 5.5 (Sep 28) are now in the model menu●RN 0.88 — The stable release was still unpublished on its Oct 12 target. rc.4 (Oct 7) is the newest; 0.87.1 is Latest●Q&A — Readers report expo-widgets showing a black widget in production builds only●WINDOWS — A walkthrough shows how to ship an iPhone app from Windows only, with no Mac●NEW — Three checks to run first when notifications stop arriving in Expo Go
From Rork to $1,000/month with AdMob — A Practical Indie Developer's Monetization Playbook
How to take a Rork-built mobile app toward meaningful monthly AdMob revenue: category fit, getting the ATT-before-initialize order right, time-based interstitial pacing, judging by ARPDAU, app-ads.txt, and running a small portfolio.
The night impressions held steady and revenue didn't
I sat with the AdMob dashboard open late one night, unable to look away. Impressions were roughly flat against the previous month. Revenue was visibly down.
Nothing about the ad setup had changed. I assumed placement was the culprit, moved the banner, changed which transition triggered the interstitial, and burned two evenings on it.
The cause sat outside the ads entirely. I was initializing the ads SDK before the iOS consent prompt resolved. That single ordering mistake was cutting me off from the higher-paying demand.
Ad revenue is decided before placement — by the order in which consent and initialization happen. Writing that down changed how I debug revenue. These days I check outside the ad code first.
Generation tools like Rork make the implementation itself take days rather than weeks. Which is precisely why the time you save should go into making sure ads arrive correctly, not into placing more of them. That's the order this piece follows.
Rork pairs well with AdMob for three reasons: each app takes days to build, the low cost per attempt lets you test more concepts, and the React Native output takes react-native-google-mobile-ads without a fight.
Categories where ads work, and where they quietly don't
Not every app can carry an ad-supported model. The fit is mostly decided at the concept stage, before a line of code exists.
Good fit
Utilities — calculators, unit converters, QR readers, flashlights, compasses. Repeated, purposeful opens. Even low-value impressions add up when the open count is high.
Idle-time fillers — simple puzzles, time-killers, fortunes, quizzes. Several sessions a day per person, and an unusually good fit for rewarded video.
Lean-back content — wallpapers, ambient sound, meditation guides, quote collections. Long sessions and repeat viewing make banner revenue predictable. I run apps in this space, and most of my day-to-day operational instinct comes from there.
Light learning — flashcards, spaced repetition, phrasebooks. High retention, and revenue that compounds across months.
Poor fit
Apps used heavily every day — task managers, serious note apps, budget trackers. When someone spends ten minutes a day inside your app, every ad is friction. They either move to your paid tier or to an ad-free competitor. Subscription is the honest model here.
Narrow professional tools — industry-specific managers, specialty calculators. The audience is too small for the arithmetic to work. A paid app makes more sense.
Apps where conversation is the point — chat, community. An ad in the middle of a conversation gets resented regardless of how good the app is.
Settle this while you're still sketching the concept in Rork. Ask whether ads can carry it; if not, change the model or change the concept — that gets you to an answer far faster than adjusting placements after launch. I compared the models in detail in Rork App Revenue Models Compared — Ads, Subscription, and One-Time Purchase.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Which app categories actually carry an ad-supported model, which ones quietly can't, and the four traits shared by apps that reach a four-figure monthly line.
✦The order that decides your ad revenue before placement does — ATT consent before SDK initialization — plus time-based interstitial pacing and the app-ads.txt file most solo developers forget.
✦Why a portfolio of five to ten small apps beats chasing one hit, and a 90-day sequence for getting there with Rork.
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Apps that get anywhere near four figures a month tend to share four things.
Daily actives above 3,000
eCPM (revenue per thousand impressions) moves a lot by category and country, but $2-$5 is a reasonable planning figure for display formats. At three to five ad views per person per day, a $1,000 month needs roughly 3,000-5,000 daily actives. That usually means install totals in the 50K-100K range and 15K-30K monthly actives.
D7 retention above 20%
The share of people still opening the app a week after install. Below 20% you have to keep buying installs to hold the numbers, and the acquisition cost eats the ad revenue it was meant to protect.
Almost all the leverage lives in the first session: deliver something valuable inside 30 seconds, and have something fresh waiting on the second open. Those two changes move the number more than anything downstream.
English alongside your own language
Japan sits mid-table on eCPM globally. US and Western European inventory runs two to three times higher, so whether you shipped in English determines the order of magnitude, not the margin.
Build both from the start in Rork. Retrofitting localization later costs several times more effort than including it in the first version.
A deliberate tracking-consent design
Since iOS 14, a declined App Tracking Transparency prompt lowers what your inventory is worth. The wording and the timing genuinely move the opt-in rate, so it's worth treating as a design problem rather than a checkbox. That's the next section.
Get the ATT consent before you initialize
This is the mistake from the opening. If the Google Mobile Ads SDK initializes before the ATT result resolves, that launch never picks up the consent-aware configuration. Fixing the order stops the bleeding on its own.
Start with app.json — the ad IDs and the consent copy:
{ "expo": { "plugins": [ [ "react-native-google-mobile-ads", { "androidAppId": "ca-app-pub-XXXXXXXXXX~YYYYYYYYYY", "iosAppId": "ca-app-pub-XXXXXXXXXX~YYYYYYYYYY" } ], [ "expo-tracking-transparency", { "userTrackingPermission": "Used to keep the ads you see relevant. Declining changes nothing about the features available to you." } ] ] }}
That string does more for your opt-in rate than any other single line in the project. Saying what the data is for, and that declining costs the person nothing, reads better than asking for permission to track.
Then the initialization itself:
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() { // On iOS, let the ATT result resolve before initializing 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();}
Two notes on why it's written this way. The getTrackingPermissionsAsync check comes first so that later launches don't attempt a prompt that iOS will never show again — ATT can only be answered once, so you only request while the status is UNDETERMINED.
And the first ad request waits on initialize(). Running initialization behind the splash screen, then loading the first ad after the home screen paints, has been the steadiest sequence for me.
One boundary on where the prompt appears. Never on the first screen. Asking before someone knows what the app does is asking to be declined. Wait until the first piece of value has landed — a wallpaper saved, one result viewed — and the opt-in rate goes up.
Placement patterns, and pacing by time rather than count
Placement itself settles into three shapes.
Pattern A — launch interstitial plus a persistent banner
The simplest arrangement. One full-screen ad at cold launch, a banner anchored to the main screen. Middling revenue, but light enough to be right for a first app.
ANCHORED_ADAPTIVE_BANNER picks its height from the device width. Layouts break less often than with fixed sizes, which cut down the time I spend nudging spacing.
Pattern B — completion interstitial plus rewarded unlock
The right shape for games and puzzles. Full-screen ad on level clear, rewarded video to unlock extras. Revenue per person runs roughly 1.5x to 2x Pattern A.
Rewarded video lets the person decide whether to watch. High value per view and low cost to the experience — no other format gives you both at once.
Pattern C — native ads inside a content list
The right shape for anything people scroll through. One slot every five to seven items. Click-through runs higher than display formats, sometimes two to three times the revenue of the other patterns.
Pace by time, not by count
This is where I got it wrong first. I capped interstitials at "one in every three actions," which sounds reasonable until someone flicks quickly between two screens and sees two full-screen ads inside a minute. A count says nothing about how it feels.
Now I hold a cooldown instead:
import AsyncStorage from "@react-native-async-storage/async-storage";const COOLDOWN_MS = 4 * 60 * 1000; // 4 minutesconst 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(); // preload the next one return true;}
The timestamp goes to AsyncStorage so that closing and reopening the app doesn't reset the interval. Keep it in a module variable and you have effectively built an ad that fires on every launch.
When interstitial.loaded is false, the call quietly does nothing. Making someone wait on a network round trip to be shown an ad has never been worth it in anything I run.
Judge by ARPDAU, not eCPM
For a while I evaluated placement changes on eCPM alone. Unit value up, monthly total down — after a few months like that, I changed the metric.
I look at ARPDAU now: revenue divided by daily active users. Adding ads moves eCPM and impressions, but it also moves churn, and only a per-user figure carries both.
Metric
What it shows
What it hides
eCPM
Value of a single slot
Volume and churn
Impressions
How many ads you served
Value and experience cost
ARPDAU
Value × frequency ÷ users
Long-term retention (pair it with D7)
Put ARPDAU and D7 side by side, and only add ads where neither one falls. Once you're ready to have several networks bid against each other, I wrote that up in Running AdMob Bidding in Production on a Rork App.
app-ads.txt and ASO — the two things upstream of revenue
Even with correct ad code, two upstream leaks are easy to miss.
Publish app-ads.txt
It declares who is allowed to sell your inventory. Host app-ads.txt at the root of the developer website listed on your store page, containing the line AdMob shows you:
Without it, buyers configured to purchase only from authorized sellers cannot bid on you at all. I saw the upper end of my rates improve after publishing it. The domain has to match the website URL on the store listing or the file is never read, so verify that once after launch.
Be findable
Building the app is only part of it. On iOS the 100-character keyword field decides your search traffic; on Android it's the vocabulary inside the description.
Screenshots are the only ad slot you get inside the store. I use the five like this:
Slot
Job
1
The single most important experience, plus a short line of copy
2
Main features in one frame
3
What makes it different from the alternatives
4
A user's words (review excerpt)
5
Pricing clarity — free, ad supported
Ask for a review only at a moment where satisfaction is likely:
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();}
The isAvailableAsync and hasAction checks keep you from spending a call in an environment that won't show anything. Apple allows at most three prompts per person per year, so there is very little room to waste.
Why a portfolio beats chasing a hit
Reaching a four-figure month across five to ten apps is more realistic for a solo developer than reaching it with one. Three reasons.
Risk gets divided. A single app can be undone by a policy change, a review, or a competitor launch. Spread across ten, losing one or two barely moves the total.
Learning accumulates. Ten apps teach you which categories, placements, and store vocabulary actually matter. One app leaves you unable to tell luck from a repeatable pattern.
Rork makes the volume plausible. One to two weeks of focused weekend work per app puts one or two a month within reach — six to twelve over half a year.
My own portfolio leans toward lean-back apps: wallpapers, ambient sound, that sort of thing. It doesn't feel like one app carrying the rest so much as the total quietly holding the floor.
Pairing ads with other revenue
Ads alone have a ceiling. What raises it is almost always something other than ads.
A remove-ads purchase. A one-time unlock at a few dollars. The point is less the revenue than giving people who dislike ads an exit that keeps them in the app. Purchase rates run at a few percent, which matters once the base is large.
A subscription for extra content. Lean-back apps can offer exclusive wallpapers or sound packs monthly. Store fees take their cut, but recurring revenue behaves differently from impression revenue.
Recommendations that fit the moment. In a utility app, suggesting the one thing that genuinely belongs in that context reads as help rather than interruption. Do it more than once per screen and it stops working, so I keep it to a single slot.
A 90-day sequence, and what I decided not to do
Days 1-14 — Draft three concepts in good-fit categories. Download three to five competitors for each and actually use them. Pick the one where you can be different, and build v1 in Rork.
Days 15-30 — Integrate AdMob (banner, interstitial, rewarded) with the ATT ordering in place. Ship in two languages, finish screenshots and store copy, submit to both stores. Publish app-ads.txt here too.
Days 31-45 — Iterate keywords, gather reviews, build initial daily actives. Start concept and build for app #2 in parallel.
Days 46-70 — Read ARPDAU and D7 from app #1 and apply both to #2. Start #3, and ship a substantial update to #1.
Days 71-90 — Three apps live, combined daily actives in the 1,000-2,000 range. This is where you find out whether the cadence for #4 onward is sustainable.
Three things I decided not to do along the way.
Don't stack ads. Launch, every transition, every completion, before every view — that app gets deleted. The month I reduced frequency, revenue did fall. Six months later it had settled above where it was before the cut. The only real question was whether I could sit through the months in between.
Don't chase short-term unit value. Slotting rewarded video into a flow that didn't need it, purely because the rate is higher, reads as "you can't use this app without watching an ad." Once someone reads it that way, the ratings don't come back.
Don't swing for one big win. Investing months into one ambitious app is the riskiest pattern available to a solo developer. Nobody predicts which app lands, no matter how long they've been doing this. Ship small, watch the data, fix. Slower on paper, faster in practice.
Where to start
Pick one ad-supported app you already open every day. Write down which ad moments you accepted and which ones annoyed you. That note is a better design brief than anything I could hand you.
Then, whatever else you change later, keep the ATT-before-initialize order from your very first build. Most things can be fixed afterward; this one is far cheaper to get right at the start.
Thank you for reading this far.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.