Adding a bandwidth SDK to a React Native app
React Native dominates the cross-platform mobile space outside Flutter. The monetization options that ship with the default RN ecosystem are the usual suspects: ads, IAP, and subscriptions. This guide covers something different: integrating an opt-in background bandwidth revenue layer into a React Native app, with proper native module wiring, sensible consent UX, and no impact on the JS thread or React reconciliation.
If you ship an RN app with active users, this is one of the lowest-effort additions you can make to your revenue stack. The footprint on the JS side is a few lines; the native side is autolinked.
Why this matters for React Native specifically
React Native apps tend to ship to a mix of mobile devices with varying session lengths. The default monetization advice — banner ads from the major networks — produces frustratingly small revenue for indie scale, and interstitials punish retention. IAP works in some categories and not others.
What works durably across an RN audience is a background revenue layer that does not depend on user interaction. The user opens the app, uses it, exits to the launcher; the SDK continues to earn while the device is online, on its own native thread, with no impact on the JS bridge.
For a React Native app with 10,000 monthly active users, the background bandwidth layer typically earns between $300 and $1,500 per month depending on regional mix and uptime. That sits alongside any IAP or subscription revenue without competing for it.
Integration on the JS side
The SDK ships as an npm package with autolinked native modules for Android and iOS. A minimal integration looks like this:
// App.tsx
import React, { useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import GetPassive from '@getpassive/react-native';
export default function App() {
useEffect(() => {
(async () => {
await GetPassive.initialize({ apiKey: 'YOUR_KEY' });
const accepted = await AsyncStorage.getItem('terms_accepted');
if (accepted === 'true') {
await GetPassive.start();
}
})();
}, []);
return /* your app */;
}
The JS-side API is a thin wrapper over the native module bridge. initialize() sets the API key and prepares the native side. start() begins the background work, gated on the user’s consent state. stop() is called when the user revokes from settings.
The bridge traffic is minimal: an initialize call, a start call, and occasional state updates. The actual work runs on the native side, on its own thread, with explicit yielding under sustained CPU load. The JS thread is untouched.
Expo and EAS Build considerations
Expo Go does not load custom native modules at runtime, so the SDK does not work inside Expo Go. For Expo projects, the path is one of:
- EAS Build with a development build. This is the recommended approach for active Expo projects. You stay on Expo, but the app binary is built with native modules included.
- Bare workflow. Ejected projects work like a standard React Native app. The SDK autolinks normally.
For a fresh Expo project, follow the EAS Build path. The SDK’s native module is a small dependency added to the development build and shipped through the same EAS pipeline you already use for releases.
Consent UX in React Native
The pattern that works in React Native is the same as everywhere else: bundle the disclosure into your Terms and Conditions, accept consent at first launch, persist the state in AsyncStorage (or your preferred storage layer), and gate start() on the persisted state.
A clean Settings screen entry looks like this:
// SettingsScreen.tsx
import { Switch } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import GetPassive from '@getpassive/react-native';
export function BandwidthRevenueToggle() {
const [enabled, setEnabled] = useState(false);
const onToggle = async (next: boolean) => {
setEnabled(next);
await AsyncStorage.setItem('terms_accepted', next ? 'true' : 'false');
if (next) {
await GetPassive.start();
} else {
await GetPassive.stop();
}
};
return <Switch value={enabled} onValueChange={onToggle} />;
}
This is the only consumer-facing UI the SDK needs. There is no embedded modal from the SDK, no banner, no badge. The user sees the disclosure in your T&Cs and the toggle in your settings.
What this looks like in production
A typical production rollout for a React Native app:
- Apply for a developer key. Test mode is issued on approval.
npm install @getpassive/react-nativeand rebuild the dev client.- Add the
initialize()andstart()calls in your bootstrap. - Update T&Cs and privacy policy. Add Play Console data safety entries.
- Ship the settings toggle.
- Run in test mode with a small cohort for two weeks.
- Promote to live.
The integration is small. The interesting work is everything else — consent language, settings UX, and the app itself.
Realistic earnings for an RN app
The ranges:
- 1,000 MAU, mixed regions: roughly $30–$150 per month.
- 10,000 MAU, mixed regions, normal usage: roughly $300–$1,500 per month.
- 50,000 MAU, with healthy uptime: roughly $1,500–$6,000 per month.
- RN app with significant US/UK/EU concentration: sit at the top of the range.
Earnings scale with active devices, country mix, and uptime. Long-running RN apps (anything with persistent background presence under platform rules) earn more per device than short-session apps. We cover the variables in the bandwidth monetization earnings guide.
The takeaway
React Native apps benefit from the same background revenue model as native apps, with a small JS-side footprint and a native module that does the actual work. The integration is autolinked, the bridge traffic is minimal, and the consent fits naturally into the T&Cs the user already accepts at first launch. For RN apps with active users, it is one of the highest-return revenue layers you can add for a single sprint of work.
For related reading, see our cross-platform revenue guide, the SDK performance impact analysis, and the ad-free monetization guide.
FAQ
How does a bandwidth SDK integrate into a React Native app?
As a standard React Native native module: a JS-side API in your application code, native Android and iOS implementations underneath. You autolink the package, call initialize and start once, and the native side handles the rest on its own thread without blocking the JS thread.
Will this slow down the React Native bridge or affect UI responsiveness?
No. The SDK does not run on the JS thread or the bridge. All work happens on a native background thread, with explicit yielding under sustained CPU load. Bridge traffic is minimal: an initialize call, a start call, and the occasional state change.
Does this work with Expo?
Yes for the bare workflow and EAS-built projects. Expo Go does not load custom native modules at runtime, so the SDK does not work inside Expo Go. Eject to a development build or use EAS Build to produce an app binary with the native module included.
What about Hermes versus JSC?
Both engines work. The SDK does not depend on a specific JS engine because it does its work on the native side. The JS-side API is a thin wrapper over a native module bridge.
How is consent handled in a React Native app?
Bundle the disclosure into the Terms and Conditions the user accepts at first launch. Read the consent state from AsyncStorage or your preferred persistence layer. Call start() only when consent is granted. Ship a settings toggle that calls stop() on revoke.
Apply as a developer
Sign up free and we will review your app category, consent flow, and rollout plan before issuing developer keys.