We tagged and categorized every customer support ticket that came into Applighter for twelve months. Not tweets. Not surveys. Real developers, stuck on real code, mid-shipping a real app. The result is a very different picture of react native common problems than what you'll find in the annual community surveys — because people file tickets when they're bleeding, not when they're reflecting.
The short version: React Native developers don't mostly struggle with React Native. They struggle with the ten things around it — EAS credentials, Supabase RLS, Xcode signing, Android package name mismatches, Metro caches, environment variable leaks, App Store rejections for NSPhotoLibraryUsageDescription, and voice-model latency on Android. The core framework is fine. The perimeter is a minefield.
Methodology (in one paragraph so you can skip it)
We track tickets in a lightweight tag taxonomy: platform:ios|android|web, layer:build|backend|ui|auth|payment|ai, severity:blocker|degraded|question, and template:<slug>. Between the seven starter templates we sell — from the Weather App to the AI Voice Notes template — that's a few thousand tickets spread across a wide slice of the ecosystem: Expo SDK 50 through 53, Supabase, NativeWind, Stripe licensing, and a rotating cast of AI providers. We threw out feature-request tickets, refund questions, and anything that turned out to be a typo. What's left is signal: the reproducible, teachable friction that developers hit when they take a working starting point and try to ship it.
The ranked list: what React Native devs struggle with
Here's the top ten, sorted by ticket volume. The percentage is share of total in-scope tickets. We'll take each one below and say what it actually is, and what the fix looks like.
| Rank | Problem | Share | Layer |
|---|---|---|---|
| 1 | EAS Build fails on first run | 18.4% | build |
| 2 | Supabase RLS returning empty arrays after auth | 12.1% | backend |
| 3 | Environment variables not reaching the native binary | 9.7% | build |
| 4 | Metro bundler cache poisoning after dependency bump | 7.8% | build |
| 5 | iOS Info.plist permission strings missing → App Store reject | 6.9% | ios |
| 6 | Android package name conflicts on rename | 6.2% | android |
| 7 | Push notifications silently not delivering on Android | 5.8% | native |
| 8 | AI streaming stalls / dropped tokens | 5.1% | ai |
| 9 | Stripe webhook not firing to Supabase Edge Function | 4.4% | payment |
| 10 | Voice transcription latency on mid-range Android | 3.9% | ai |
The rest of the tail (about a quarter of volume) is spread across dozens of one-off issues: font loading order, NativeWind class caching, Reanimated worklet crashes on old Fabric versions, and the ever-popular "why is my white screen white."
1. EAS Build fails on first run (18.4%)
The #1 problem people hit isn't in React Native at all. It's the first time they try to actually build an installable binary.
The ticket pattern is almost always the same: they clone the template, run bunx expo prebuild, then eas build --profile development --platform ios — and the build dies in the "Install pods" step with a version mismatch. Or worse, it dies in a Fastlane step with a signing error, and Fastlane's error messages are famously not aimed at humans.
The root causes cluster into three buckets:
-
Expo SDK vs.
newArchEnabledmismatch. The dev cloned an SDK 50 template into an SDK 53 CLI without runningexpo doctor. -
iOS Simulator vs. device profile confusion. People pick
--platform iosand don't realize the default profile targets a physical device with provisioning that hasn't been set up yet. -
Missing
EXPO_TOKENin CI. If you're building from a CI job, EAS won't authenticate.
What we did on our side: every template config now pins Expo SDK versions inside package.json, plus a preflight script (scripts/preflight.sh) that runs expo doctor and blocks the build if anything is red. Ticket volume in this category dropped 42% quarter-over-quarter after we shipped the preflight.
Actionable takeaway: before you file a Discord post, run bunx expo doctor and paste the output. Nine times out of ten, it names the file that's wrong.
Docs: the official Expo troubleshooting page at docs.expo.dev/build-reference/troubleshooting covers most of the actual error strings.
2. Supabase RLS returns empty arrays after auth (12.1%)
The second-most-common ticket makes people think Supabase is broken. It isn't. Their Row Level Security policy is silently doing exactly what it was designed to do: filter out every row that doesn't match auth.uid(), including the ones they think should match.
The ticket almost always reads: "I'm signed in, I can see my user ID, but select * from projects returns []." And then a screenshot of the Supabase table, full of rows.
The fix falls into four checks:
- Is your
Authorization: Bearer <access_token>actually being sent? Log the raw fetch headers, not the client abstraction. Half the time the token isundefined. - Does your RLS policy use
auth.uid()orauth.jwt() ->> 'sub'? The two behave differently when you have anon-key requests mixed in. - Are you querying with the
service_rolekey in a client bundle? (You should not be. But we do see it.) - Is your session refreshing? Sessions expire in an hour by default. If the client isn't refreshing, subsequent requests silently fall to
anonrole.
For everyone shipping a Supabase-backed app on iOS or Android, we wrote up the full pattern in our guide to Supabase auth in React Native. Bookmark that if you're touching auth.
The Supabase docs on RLS at supabase.com/docs/guides/database/postgres/row-level-security are the canonical reference — but the mental model shift ("policies are WHERE clauses that always run") is the hard part.
3. Environment variables don't reach the native binary (9.7%)
This one is sneaky. Everything works in Expo Go. Everything works in the simulator. Then the developer runs eas build, installs the standalone .ipa, launches — and the API base URL is undefined.
Root cause: Expo does not automatically pull .env into a production build. You have to declare secrets in EAS Secrets or push them via eas.json env blocks, and you must prefix runtime-safe variables with EXPO_PUBLIC_. Anything without that prefix is dropped from the client bundle at build time.
The tell: console.log(process.env.API_URL) prints in Metro but is undefined in the standalone app. The pattern almost always resolves by renaming API_URL → EXPO_PUBLIC_API_URL and re-building.
4. Metro cache poisoning after dependency bump (7.8%)
You updated a package. The app crashes with an inscrutable error. Nothing you do fixes it. Then a friend on Discord says "try --reset-cache" and it works.
We see this so often that our templates now ship with a bun reset script that runs:
watchman watch-del-all
rm -rf node_modules ios/Pods .expo
bun install
bunx expo prebuild --clean
If you don't have this scripted, you'll type it wrong at 2am and blame yourself. Don't. Metro's cache invalidation is genuinely aggressive and genuinely wrong sometimes. React Native itself is famously an "if in doubt, wipe caches" ecosystem — Metro's own docs admit as much.
5. iOS Info.plist permission strings (6.9%)
The App Store review rejection nobody sees coming. Apple's automated review will reject builds that use the camera, microphone, photo library, or location and don't have a corresponding NSXxxUsageDescription string in Info.plist.
The ticket flow: dev submits build to TestFlight, it uploads fine, they get an email saying "your app has been rejected," and the reason is something like "your app uses the camera but doesn't say why."
The fix is easy. Add the strings to your app.json:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "This app needs camera access to scan documents.",
"NSMicrophoneUsageDescription": "This app records voice notes."
}
}
}
}
The mistake we most often see is developers writing generic strings ("This app uses the camera"). Apple has started rejecting those too. Say why, in a sentence a regulator could quote back.
6. Android package name mismatch on rename (6.2%)
The template ships as com.applighter.starter. The developer wants com.acmecorp.myapp. They change it in app.json and half the Android build system stops working.
The problem: on Android, the package name lives in about six places — app.json, android/app/build.gradle, AndroidManifest.xml, the directory structure under android/app/src/main/java/, google-services.json (if you use Firebase), and the OAuth redirect URI. Change it in one, forget the others, and the build succeeds but the app crashes on launch with a ClassNotFoundException.
Our renaming guide covers every location, but honestly the safest move if you're on managed Expo is: delete the android/ folder, change the config, run expo prebuild --clean. Let the tooling regenerate it.
7. Android push notifications silently not delivering (5.8%)
The insidious one. Everything looks like it's working: expo-notifications returns a valid Expo push token, you send a POST to the Expo push API, you get back "status": "ok" — and the notification never appears on the device.
The Android-specific tickets almost always trace to one of these:
- Missing FCM setup. Expo's push service is a proxy; on Android it still needs your FCM credentials configured in the Expo dashboard.
- Battery optimization killing the process. Xiaomi and Samsung devices in particular have aggressive background restrictions that suppress notifications from apps the user hasn't opened in 24 hours.
- Notification channel not created. Android 8+ requires you to create a channel before you can post to it.
We covered the full stack in The Definitive Guide to Push Notifications in Expo. If you're shipping push, read that end-to-end before you spend a week debugging.
8. AI streaming stalls or drops tokens (5.1%)
Specific to the AI templates — the Chat with PDF template, the AI Voice Notes template, and the AI Calorie Tracker.
The pattern: streaming works locally with fetch + getReader(), but on the device it stalls after a few hundred tokens or drops the last chunk. Almost always the culprit is React Native's fetch polyfill: it doesn't support ReadableStream the way the browser does. You need to use expo-fetch or the underlying XMLHttpRequest progress events, and buffer manually.
We walked through the full pattern in Streaming AI Responses to React Native. The short answer: expo/fetch or a Server-Sent Events shim. Don't fight the platform fetch.
9. Stripe webhook doesn't fire to Supabase (4.4%)
Stripe → Supabase Edge Function webhooks failing is almost always one of three things:
- Signing secret from Stripe dashboard doesn't match the one in the Edge Function env.
- Edge Function is behind Supabase's default auth. You need to explicitly mark it public in
supabase/config.toml. - The Stripe webhook endpoint is pointing at the deploy branch preview URL, not production.
These are boring, but they're a nontrivial share of our support load — enough that we now include a test-webhook.sh script that fires a dummy checkout.session.completed event so you can verify end-to-end before you go live.
10. Voice transcription latency on mid-range Android (3.9%)
The last one on the list is a hardware reality more than a bug. Real-time voice transcription (Whisper, Deepgram, whatever) on a $300 Android device is slower than on an iPhone Pro. Users assume the app is broken. It's not — it's just that the STT model is either running on a slower CPU or over a slower network connection to a cloud endpoint.
The fixes we recommend:
- Chunk audio at 4-second intervals instead of 10 (feels more responsive even if wall-clock latency is the same).
- Show a token-level typing indicator so the user knows the app is alive.
- Fall back to a smaller model tier for older devices.
We build this fallback into the AI Voice Notes template by default: deepgram-nova-2 for iOS ≥ iPhone 12 and modern flagship Android, whisper-small for everything else, config-driven.
What this tells us about the ecosystem
The pattern is clear: React Native itself, the framework, is not what most developers struggle with. The framework has stabilized. What developers hit is the seam between the framework and everything around it — build tooling, native permissions, backends, push infrastructure, AI providers, and platform-specific device quirks.
That's why we started Applighter in the first place. A UI template that gives you screens doesn't solve any of these ten problems. A full-stack template that ships with a working Supabase backend, a working push notification setup, a working Stripe webhook, and a working streaming AI pipeline solves eight of them out of the box.
Here's how the two approaches compare in terms of what tickets you'd actually file:
| Problem | Blank Expo template | UI-only kit | Applighter template |
|---|---|---|---|
| EAS build config | You write it | You write it | Preflight-scripted |
| Supabase RLS policies | You write them | Not included | Included + tested |
| Push notifications | You wire it | Screens only | Full FCM + APNs |
| Stripe → Supabase webhook | You build it | Not included | Ships with test script |
| iOS Info.plist strings | You add them | Missing | Pre-declared |
| Streaming AI | You debug fetch | Not applicable | expo/fetch by default |
| Env variable propagation | You learn the hard way | Same | Documented in README |
Some folks would rather build these seven layers themselves and learn everything on the way up. That's a completely reasonable choice. Others would rather buy the layers and spend their time on their actual product idea. That's also reasonable. What isn't reasonable is buying a "template" that gives you nothing but screens and thinking you've bought a foundation. (For an honest look at that market, see reactnativetemplates.com and similar aggregators — most of what's listed is a UI kit calling itself a template.)
What we changed based on the data
Because we get to see this data and most template vendors don't, we made concrete changes over the year:
- Added
bunx expo doctorand a preflight script to every template'spostinstall, killing 42% of build-related tickets. - Shipped a
docs/troubleshooting.mdin every template repo with the exact ten problems above, pre-answered. - Added a
bun resetscript so the cache-poisoning ticket has a one-command fix. - Pinned Expo SDK versions and included an upgrade path doc for the next minor.
- Baked FCM and APNs setup into every template's setup guide with real screenshots, not "consult the Expo docs."
- Ship RLS policies as part of the initial migration — no dev should have to invent them.
None of this is glamorous work. It's the exact same set of concerns a real production engineering team would prioritize — because "hard to ship" is a bug, not a marketing feature.
Takeaways for developers
If you're building a React Native app right now, the empirical priority order for what you should invest a day learning:
-
EAS Build config — this is where you'll die first. Learn
eas.jsonand profiles before you write your second screen. - Supabase RLS — or whatever backend auth model you're using. Get the mental model right on day one.
- The env variable propagation model — because "works in dev, undefined in prod" is a 3am problem.
- The Metro cache reset command — put it in a script.
- App Store rejection rules — read Apple's list of common rejections before submission.
If you're building on top of a starter, pick one whose vendor tracks this data and iterates on it. If your template hasn't had a commit in 4 months, that's not "stable" — that's "abandoned." See our honest comparison of the market for how to evaluate.
FAQ
What is the most common problem React Native developers face in 2026?
Based on our year of support ticket data, EAS Build failures on first run are the single most common category (18.4% of tickets). The core framework issues are far less common than the tooling around it.
Are Expo templates production ready?
Expo's own official templates are production ready as starting points but are intentionally minimal. Third-party templates vary enormously — some ship full backends, tests, and push notification setup; others ship only screens and call themselves templates. Read the source before you buy.
Why do Supabase queries return empty arrays even when the table has rows?
Almost always because Row Level Security policies are filtering the rows out. Confirm the Authorization header is being sent, verify auth.uid() matches the row's owner column, and log the raw request to check the session hasn't expired.
How do I stop my Expo app from breaking after upgrading dependencies?
Run bunx expo doctor after every upgrade. Pin your Expo SDK version explicitly in package.json. Clear Metro cache with a scripted reset command rather than typing it by hand.
Is React Native still worth using in 2026?
Yes — the framework is more stable than ever and the ecosystem has matured on debugging, performance, and toolchain. The remaining friction is in the seam between the framework and native platform/backend infrastructure. That's solvable with better templates and better tooling — which is exactly what we spend our year building.
Ready to skip the ticket queue and start with a template that already handles the top ten? Browse the Applighter catalog or read our refund policy if you want to try one risk-free.
Top comments (0)