
Start on Expo, and build for the day you eject
Every React Native project opens on the same fork: Expo's managed workflow, or the bare workflow where you own the ios/ and android/ projects outright. Most write-ups turn it into a personality quiz. Pick managed if you like speed, pick bare if you like control. That framing is the thing that bites you six months in.
Here is the friction that actually decides it. You start managed because it is fast and you want to ship. Months later you need one native module Expo's SDK does not wrap, or a build setting EAS will not expose. The "speed vs control" tradeoff stops being hypothetical and becomes a migration, and how much that migration hurts was set the day you ran create-expo-app, not today.
Managed vs bare is the wrong frame. The decision that matters is when you pay for control, and how much it costs when the bill comes due.
Managed is renting your build harness
Expo's managed workflow is a trade. You hand over the native projects and the build pipeline, and in return you get EAS cloud builds, over-the-air updates, native config without opening Xcode, and Expo Go for instant device testing. It is a genuinely good trade. You are renting the harness that turns your JavaScript into signed binaries, and for most of a project's life renting is correct. You would not stand up your own CI cluster to build an MVP.
The cost is not a feature you lose on day one. It is that the harness is not yours. When you need something the rental does not offer, you cannot just edit it. You have to take ownership of the whole thing at once.
The one decision: make the eject a diff, not a rewrite
Here is the core design decision, in one sentence: start managed, and keep the project so that the day you run expo prebuild is a one-afternoon diff instead of a multi-week rewrite.
expo prebuild generates the native ios/ and android/ projects from your app.json config and your dependencies. Modern Expo (continuous native generation) is built so that line is no longer a cliff. This is not the burn-the-boats expo eject of 2019. After prebuild you keep expo-modules-core, EAS Build, and EAS Update. You prebuild, drop in the one native thing you needed, and carry on.
What makes the eject cheap is discipline you apply before you need it: keep native changes in config plugins instead of hand-editing generated files, and prefer libraries that ship Expo config plugins. What makes it expensive is hand-editing the generated native code, so the next prebuild silently overwrites your changes and you are debugging a regenerated project at 2am.
What the eject is and isn't
It is not a divorce from Expo. After prebuild you still have most of the SDK, EAS Build, and OTA updates. You have just taken ownership of the native projects so you can reach the module the managed flow could not.
It is not free either. Once you commit ios/ and android/, you own native upgrades. Bumping the React Native version stops being a line in package.json and starts involving native diff churn across both platforms. That is the actual recurring price of control, and any honest version of this decision names it instead of pretending bare is just "more powerful."
The wrong prior: that you choose once, at the start
The mistake I see teams make is treating this as a permanent identity picked on day one. "We're an Expo shop." "We run bare." It is not an identity. It is a point on a timeline where you buy the harness back, and the only decision that matters is keeping that point cheap to reach.
So start managed, almost always: you get to a shipping build without onboarding anyone to Xcode and Android Studio, and you get OTA from day one. The cases where you start bare are narrow and you will know them: you already depend on a native SDK with no Expo support, or you are dropping React Native into an existing native app.
So: start managed, keep the eject cheap
Start on Expo. Get to a build people can install before you spend a dollar on build infrastructure you own. But from line one, treat the native projects as something you will eventually own, and keep your config in plugins so prebuild stays a diff. The teams that get burned are not the ones who chose managed. They are the ones who chose managed and then pretended the eject would never come.
Take what's useful, change the rest.