Skip to content

Seven years shipping cross-platform apps: what I actually learned

· 4 min read · Amrith Vengalath

  • React Native
  • Career
  • Release Engineering

I've spent about seven years now building cross-platform mobile apps, mostly React Native with some Flutter, and shipping them to real users on the App Store and Google Play. This isn't a how-to. It's the set of things I believe now that I didn't believe - or hadn't learned - when I started. Some of them I'd argue with a younger version of myself about.

Shipping is the hard part, not building

When I started, I thought the job was writing features. The features are maybe half of it. The other half is everything between "it works on my machine" and "a user in another country, on a three-year-old phone, on a bad network, has it working." Release pipelines, code signing, crash monitoring, staged rollouts, OTA fixes - the unglamorous release-engineering layer is where a lot of the real reliability comes from. I spent my early years undervaluing it. I'd tell anyone newer: invest in the boring infrastructure early. It pays back every single release.

"It builds" means almost nothing

The simulator on your fast laptop is a liar. So is "it works on my iPhone." Two platforms behave differently, real devices are slower and weirder than you expect, and the bugs that matter show up on hardware and networks you don't own. I test on real devices, both platforms, as a default now - not as a final QA step but throughout. The number of "works on my machine" issues that simply stop happening when you do this is hard to overstate.

Measure, don't guess, especially for performance

Early on I "optimized" things that felt slow and felt productive doing it. Half the time I was tuning something that wasn't the bottleneck. Performance work without measurement is just rearranging code. Put a number on it - cold start, time to interactive, crash-free users - change one thing, look at whether the number moved. It's slower and less satisfying than heroically rewriting something, and it's the only approach that actually works.

The unhappy path is the job

The demo is the happy path, and the happy path is the easy 20%. The payment that succeeds but loses connection before the callback. The notification tapped while the app is dead. The user who denied permissions. The write that happened offline. Every feature I've shipped that mattered was 80% handling the cases that aren't the demo. Junior me built the happy path and called it done. Senior me assumes the happy path is the warm-up.

Upgrades are cheaper when they're small and frequent

The biggest, most painful upgrades I've done were the ones I'd put off for too long. Nine versions of React Native at once is a project; one version at a time is a Tuesday. The same is true of dependencies. Staying close to current is annoying in small doses and brutal in large ones. Falling behind doesn't avoid the work - it compounds it and adds risk.

Pick boring, recognizable solutions

I've learned to value code the next person instantly recognizes over clever code that's marginally better. Reaching for the well-known library, the standard pattern, the obvious structure - even when I could hand-roll something slightly nicer - pays off because the codebase outlives my memory of why I was clever. Boring is a feature in code you have to maintain for years.

The tools change; the fundamentals don't

In seven years I've watched the bridge give way to the New Architecture, Flipper come and go, debugging move engines, OTA providers rise and sunset, state-management fashions cycle. If I'd tied my identity to any specific tool I'd have been repeatedly stranded. What carried over was the fundamentals: how rendering works, why the network is untrustworthy, what makes apps start fast, how to read a crash, how to ship safely. Learn the tool you need today, but invest hardest in the things underneath it that don't churn.

What I'd tell someone starting out

Care about the release pipeline as much as the features. Test on real devices, both platforms, always. Measure before you optimize. Assume the unhappy path is the actual work. Upgrade little and often. Prefer boring, recognizable solutions. And don't get too attached to any single tool, because the one you love today will be deprecated eventually - the engineer who understands the layer beneath it will be fine.

None of this is novel. Most of it is the kind of thing you read as a beginner, nod at, and don't really believe until you've been burned a few times. I've been burned. Now I believe it.