I have been releasing apps as a solo developer for twelve years, and the experience of "it worked in development, it broke at release" still happens to me on almost every project. With tools like Rork that generate a lot of the code for you, there is an additional class of pitfall. The generated code often looks correct and behaves correctly in the preview environment, but breaks in conditions that only exist in production.
This article walks through six specific pitfalls I keep hitting when shipping Rork-built apps to the App Store and Google Play, along with the concrete things I now do to avoid each one. These are things you will not find in the official docs because they come out of having actually tripped over them.
Pitfall 1: API keys wired through preview-friendly variables
Rork's preview environment is generous with EXPO_PUBLIC_* environment variables. The trap is that AdMob unit IDs, Stripe publishable keys, and similar values often stay tied to those preview variables, and the production build ships without the values being swapped out.
I once released an app with AdMob's test unit ID still in place. I noticed the following morning when I saw "revenue: 0" and realized the test ID had been serving test ads to real users for hours.
The workaround I now run every time:
grep -rn "ca-app-pub-3940256099942544" src/ # AdMob test ID
grep -rn "pk_test_" src/ # Stripe test key
grep -rn "localhost" src/ # dev server URLAny hit here is a blocker for a production build. I have this wired into CI so a failed grep fails the build.
Pitfall 2: Permission dialog text that is not localized or is empty
Rork tends to leave permission prompts in English and sometimes leaves them empty. On iOS, a missing or empty NSXxxUsageDescription string will actively crash the app when the permission is triggered. In preview, you may never see it. In production, it is a crash report.
The fix is to spell these out explicitly in app.json:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "Used to take photos of your artwork",
"NSPhotoLibraryUsageDescription": "Used to select images of your artwork",
"NSLocationWhenInUseUsageDescription": "Used to suggest nearby art spots"
}
}
}
}Write these from the user's point of view, in terms of what they get. "We use your camera" is weak. "Used to take photos of your artwork" tells them why it is worth the permission. Apple's review process does pay attention to this.
Pitfall 3: Splash screen that sits frozen on cold start
A default Rork splash screen configuration, shipped unchanged, usually produces a noticeable gap between launch and first render. Nine times out of ten the problem is not Rork. It is that useFonts, initial API calls, and image preloading are happening sequentially when they could be parallel.
The fix is to pin the splash screen until you are ready, and run your initialization work in parallel:
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useState } from 'react';
SplashScreen.preventAutoHideAsync();
export default function App() {
const [ready, setReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await Promise.all([
loadFonts(),
loadInitialData(),
preloadImages(),
]);
} finally {
setReady(true);
await SplashScreen.hideAsync();
}
}
prepare();
}, []);
if (\!ready) return null;
return <RootNavigator />;
}A simple Promise.all around your startup work is often enough to cut cold-start time in half.
Pitfall 4: Safe Area clipping and Android back button gaps
Rork-generated layouts often look clean in the simulator but clip into the Safe Area on real hardware, especially iPhone models with notches or dynamic islands. On Android, generated code frequently omits back-button handling entirely. Opening a modal and pressing Back should close the modal, but if you did not write that handler, Back just kills the app.
For Safe Area, I use react-native-safe-area-context's useSafeAreaInsets rather than the older SafeAreaView. For Android back behavior, I make it explicit on every screen that has modal or overlay state:
useEffect(() => {
const sub = BackHandler.addEventListener('hardwareBackPress', () => {
if (modalVisible) {
setModalVisible(false);
return true;
}
return false;
});
return () => sub.remove();
}, [modalVisible]);Tedious, but this is exactly the kind of thing Android reviewers notice in their ratings.
Pitfall 5: Missing ATT on iOS completely tanks AdMob revenue
Since iOS 14, any app using AdMob has to ask for App Tracking Transparency in the right way or the ads will serve at a drastically reduced rate. Rork does not always include the ATT flow, and apps shipped without it can earn less than half of what they should.
My first shipped app with Rork had an eCPM that was roughly a third of the published rate for its category. I only figured it out after two weeks of investigating. The reason was no ATT prompt at all.
The fix has two parts. Show the prompt on first launch:
import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
async function initTracking() {
const { status } = await requestTrackingPermissionsAsync();
if (status === 'granted') {
// personalized ads
} else {
// switch to non-personalized configuration
}
}And add NSUserTrackingUsageDescription to your infoPlist in app.json. If this key is missing, the prompt never appears, and you end up thinking your users all declined.
Pitfall 6: Apple's Guideline 4.3 (Spam) on AI-generated apps
This is not Rork-specific, but it hits AI-generated apps harder than most. When Apple's reviewers see a submission that looks like "another one of those," Guideline 4.3 (Spam) is the trap door.
Three things I now treat as required before submission:
- At least one feature that is genuinely your own angle, not a generic recipe
- Screenshots that immediately communicate a unique experience, not a stock UI
- A long-form App Store description with at least three lines of your personal reason for building the app
Reviewers spot template-shaped submissions in seconds. Speed from AI and distinctiveness from you have to coexist.
Things to set up before Day 1
Two last things about the post-launch side. Put them in before the app is public.
Automatic crash reporting. Sentry or similar, from the start. Users will not report crashes. Without instrumentation, you are debugging blind.
An in-app announcement channel. Something like Firebase Remote Config driving a banner inside the app. When a bug is discovered, you cannot ship a fix through the App Store in less than about a day. You need a way to tell users "we know, we're on it" in the meantime.
Forced-update mechanism. Especially for apps with Stripe checkout or API keys in play, old versions in the wild are a liability. Keep minimum-supported-version on the server, and surface an update screen on launch when the installed version falls below it. Add this on day one, not the day you wish you had it.
One thing to try
If you have a Rork app that is close to release, close this article and run one command in its project root:
grep -rn "ca-app-pub-3940256099942544\|pk_test_\|localhost" src/
If it is silent, Pitfall 1 is already handled. If it prints anything, that is something to fix before you submit. Even in a world where AI writes most of the code, the responsibility for what ends up in the store stays with the developer. The way you grow into that role is one small checklist item at a time.