Skip to main content
CASE STUDY

ExpoGotoProduction:WhatActuallyBreaks

It ran fine in Expo Go and now it will not ship. The JavaScript is not the problem and almost none of it needs rewriting. What changes is the build and release layer underneath it, and teams hit the same four walls in the same order.

Expo Go to Production: What Actually Breaks
|August 23, 2026|React NativeExpoMobile DevelopmentApp StoreProduction Readiness

Introduction

It worked in Expo Go. That was the whole point of Expo Go. Somebody scanned a QR code, the app opened on a real phone, and the demo went well enough that the app is now a commitment. Then the release stopped. Either a library the business needs will not run, or a build fails with a native error nobody on the team can read, or an environment variable you definitely changed is still wrong in the installed app.

Here is the part worth reading before anything else. Your JavaScript is fine. Your screens, your navigation, your state, your business logic, almost none of it needs rewriting. What changes is the layer underneath the application code, the part that decides how a binary gets built, how configuration reaches it, and how releases go out. That layer barely existed while you were in Expo Go, because Expo was providing it for you.

ā— QUICK ANSWER

Why does an Expo Go app stop working when it has to ship? Because Expo Go is a pre-built app with a fixed native surface, and production needs native code that surface does not contain. The fix is a development build, not a rewrite.

  • You do not eject. That command is gone. Native projects are now generated on demand with npx expo prebuild.
  • Native modules are the usual wall. Expo Go cannot load native code it was not compiled with. No flag fixes this.
  • Environment variables are baked in at build time. Changing one and restarting the app does nothing.
  • New Architecture has been the default since React Native 0.76. The cost lands in third-party dependencies, not in your code.
  • Over-the-air updates cover JavaScript only. Native changes, new permissions and SDK upgrades all need a store build.

Everything technical below is taken from the current Expo and React Native documentation rather than from memory, because this tooling moves fast and a confident wrong version number would waste your afternoon. Where something depends on your specific setup, we say so instead of guessing.

One more thing, and it matters for how you read the rest. If an agency has looked at your Expo prototype and come back proposing a rebuild from scratch, be careful. That is almost never what the codebase needs. It is, however, a much larger engagement.

Why Can Expo Go Not Run Your App Any More?

Expo Go is an app. A real one, sitting on the store, that anybody can install. Expo compiled it, which means Expo decided at compile time exactly which native code it contains. When you scan that QR code, your JavaScript is being loaded into a container somebody else built.

That arrangement is brilliant right up until your product needs native code of its own. A payments SDK. A Bluetooth library for the hardware your company actually sells. Background location. A specific analytics package the marketing team has already signed for. Each of those ships compiled native code, and there is no mechanism to get that code into a binary that Expo controls and you do not.

So this is not a limitation you can raise a ticket about. It is the architecture doing exactly what it was designed to do. There is no configuration flag, no paid tier, and no workaround, and any answer you find suggesting otherwise is solving a different problem.

The replacement is a development build. Expo's own description is the clearest one: a development build is your own version of Expo Go. Same fast refresh, same developer tooling, except you compiled it, and it contains precisely the native libraries your project depends on. The expo-dev-client library provides the developer experience layer on top. You also get network request inspection and a launcher for switching between development servers, which matter more than they sound once several people are working on the app.

Expo states plainly that Expo Go is not intended for production deployment. It is worth taking that at face value. Expo Go is a development tool that happens to run your app, and treating it as a preview of your shipped product is what leads to the surprises in the next section.

Do You Actually Have to Eject From Expo?

No. And if you take one correction away from this page, make it this one, because the wrong answer here costs real money.

Ejecting used to be a one-way door. You ran a command, Expo generated the native iOS and Android projects, handed them to you, and from that moment you owned them forever. Every SDK upgrade afterwards became a manual merge into native directories that had drifted from anything Expo could regenerate. Teams were rightly frightened of it, and a lot of the anxiety around Expo in production traces directly back to that command.

It no longer exists. The current model is Continuous Native Generation, and the idea is a genuine improvement rather than a rename. Instead of creating native projects once and maintaining your changes to them for the life of the codebase, short-lived native projects are generated only when needed, from your app configuration and config plugins, by running npx expo prebuild. Native directories become build output rather than source you maintain. If they get into a bad state, you delete them and generate them again, which was never an option under ejecting.

The managed versus bare workflow split is deprecated framing as well. Prebuild is optional, so you can still maintain native directories by hand if you have a reason to, but it is no longer a fork in the road you commit to once.

The practical use of knowing this is diagnostic. If a consultant or an agency is telling you that you need to eject, or is pricing the work around the risk of ejecting, they are working from documentation that has been out of date for years. That tells you something useful about the rest of their advice before you have spent anything.

What Breaks Between Expo Go and a Real Production Build?

Every page on this topic lists these individually. Almost none of them explain that they are the same phenomenon, which is why they all arrive at once and feel like the project falling apart. Expo Go was hiding a production environment from you, kindly and deliberately. A release build stops hiding it.

Native modules that were never there. Covered above, and usually the first wall. It is also the one most likely to arrive as a business requirement rather than a technical choice, which is why it tends to land mid-sprint with no warning.

Configuration that was already baked in. Values you assumed were read at startup were compiled into the bundle. This one gets its own section next because of how often it ships a staging URL to production.

Permissions you never had to justify. Expo Go already held a broad set of permissions, so your code asking for the camera worked without you ever writing a justification. Your own binary requests only what you declare, and app review will ask why you want each one. A vague answer here is a common rejection.

Performance you were never really seeing. Development mode is slower, and your app runs in it the entire time you are in Expo Go. Teams sometimes discover their performance is better than they thought. Others discover a jank problem was being masked by a slow baseline they had normalised to.

Logs that stop appearing. In production mode console output no longer arrives in your terminal. If your debugging strategy was console logging, it silently stops working at the exact moment problems get harder, which is why real error reporting belongs on the pre-launch list rather than the someday list.

Deep links and navigation that behaved differently. Links routed through Expo Go's own scheme while you were developing. Your app has its own, and universal links need real association files hosted on a real domain. This tends to be found by a marketing campaign rather than by a test.

None of these are hard problems individually. They are, however, six things at once, usually landing in the week somebody promised the app would be in review, and each one is in a different discipline. That combination is what turns a two-week estimate into six.

Why Did Your Environment Variables Not Change?

You changed the value. You restarted the app. It is still wrong. Nothing is broken, and the explanation is worth understanding properly because the same misunderstanding causes a much more expensive version of this problem later.

EXPO_PUBLIC_ variables are inlined into the JavaScript bundle at build time. They are not looked up when the app starts. By the time the app is installed on a device, the literal value is already sitting inside the bundle, exactly as it was when that build was made. Editing an env file afterwards changes a file the installed app never reads.

So each environment needs its own build. Development, staging and production are three builds, not one build with three settings. That is a real change in how a team works, and it is the point where a proper build profile setup stops being optional.

The expensive version happens after launch. A wrong value in a shipped binary cannot be corrected by fixing the environment file, because the binary already contains the old one. Correcting it means a new build and a store submission, unless the change is purely JavaScript and can travel as an over-the-air update. That is the difference between a five minute fix and a two day one, decided entirely by a detail nobody knew about.

Worth stating too: the prefix is PUBLIC and it means it. Inlined values are readable by anyone who takes your bundle apart. No API secret, private key or admin token belongs there, whatever the environment. Anything that must stay secret belongs behind your own backend, which is a good architecture for other reasons anyway.

What Does the New Architecture Migration Actually Cost You?

Somebody has told you the app needs migrating to the New Architecture, and nobody can tell you what that means for the schedule. It is a fair question and it has a more reassuring answer than the phrase suggests.

React Native historically had JavaScript and native code talking across an asynchronous bridge, serialising messages in both directions. That bridge was the source of a whole category of performance problems, particularly anywhere data volume was high. The New Architecture replaces it with JSI, which lets JavaScript hold direct references to C++ objects and the reverse, removing the serialisation cost entirely. Fabric is the new renderer, TurboModules are native modules built on JSI, and together they enable things the bridge made impractical, including synchronous layout measurement and React 18 concurrent features like Suspense and Transitions.

On timing, the numbers that matter. The redesign began in 2018. It became available as an experimental opt-in in React Native 0.68, released on 30 March 2022. It has been the default since React Native 0.76. Meta has been running it in production apps at scale. So this is not a bleeding-edge migration, it is catching up with a default that has been standard for a while, and the ecosystem has had years to follow.

Where the cost actually lands. Almost never in your application code. Your components, screens and business logic are written against React and they mostly do not care what is underneath. The work is in third-party libraries that ship native code, and it is archaeology rather than engineering. Every native dependency needs checking. Most well-maintained ones have supported the New Architecture for a long time. The problems are the abandoned ones, the library that solved something awkward in 2021 and has not been touched since, and for those the options are a maintained replacement, a fork you now own, or dropping the feature.

Which means the effort scales with how many native dependencies you have and how well maintained they are, not with the size of your app. A large application with six mainstream native libraries is a straightforward migration. A small one with twenty, several of them unmaintained, is not. That is why the dependency audit belongs at the very start, before anyone commits to a date. It is cheap to run and it is the only step that can change the plan.

Can You Ship a Fix Without Waiting for App Review?

Some fixes, and knowing exactly which ones is the difference between a hotfix that goes out in minutes and one that waits on review while the problem stays live.

EAS Update is a cloud service serving over-the-air updates to apps using the expo-updates library. It updates the JavaScript, styling and image assets of an installed app between store submissions.

What travels over the air. Bug fixes and crashes in JavaScript. Copy, translations, styling and screen layouts. Rollouts to a percentage of users, which is genuinely useful for anything risky. Updates pushed from CI.

What requires a new build and a store submission. Any native code or native dependency change. Any newly requested permission, camera or location or anything else. Expo SDK upgrades. Anything at all that needs a new binary. The rule underneath is simple: over-the-air updates can change what your JavaScript does, never what your binary is.

The failure mode here is assuming the wrong side of that line under pressure. A team plans a fast rollback for a bad release, discovers the fix touches a native dependency, and loses two days they had already promised away. Establish which side each kind of change falls on before you need to know, and write down who is allowed to push an over-the-air update, because a mechanism that bypasses review is also a mechanism that bypasses your release process.

Who Should Not Migrate Off Expo Go?

Plenty of teams read a page like this, conclude they are behind, and go and adopt build infrastructure their product does not need. That is a real cost with no return, so here is the honest list of people who should close this tab.

Teams whose feature set is fully covered by the Expo SDK. Screens, navigation, network calls, camera, notifications, storage, maps. If that is your app and nothing on the roadmap needs a native library Expo has not already wrapped, you do not have this problem. You still need proper build profiles for store releases, but the migration this page describes is not yours to do.

Prototypes that are still prototypes. If you are validating whether anyone wants this, Expo Go is the correct tool and production infrastructure is a distraction from the only question that matters right now.

Internal tools that live on known devices. A demo app for your sales team does not need store submission, over-the-air update policy or credential management.

Teams already shipping without friction. If releases go out and nobody dreads them, your setup is working. An upgrade you do not need is a risk you did not have to take.

And a fourth group who should be honest with themselves in the other direction. If your app needs native modules, has real users, handles payments or personal data, and nobody on the team has shipped to both stores before, the migration is not optional and estimating it as a sprint is how the date slips twice. That is a scheduling problem more than an engineering one, and it is fixable if you see it early.

How Do You Know Whether Your Team Can Carry This?

This is the actual decision, and it is not about how good your engineers are. It is about whether anyone has done this specific thing before, because the difficulty here is almost entirely unfamiliarity rather than complexity.

Has anyone shipped to both stores before? Not written an app. Shipped one. Apple certificates, provisioning profiles, Play signing keys, review metadata, the privacy questionnaire. Every one of those is straightforward the second time and a maze the first, and none of it is React Native knowledge.

Can somebody read a native build failure? When a build breaks you get a Gradle or Xcode error, not a JavaScript stack trace. A team that has never seen these can lose days to something a mobile engineer recognises immediately.

Does a release depend on one laptop? If credentials and signing live on one machine and one person, you do not have a release process, you have a single point of failure that happens to be employed. Fixing that is a good idea regardless of who does the migration.

Has anybody audited the native dependencies yet? If not, nobody currently knows how big this is. Any estimate given before that audit is a guess wearing a number.

Two or more no answers does not mean you need outside help permanently. It means the first release will take substantially longer than whatever has been estimated, and it should be planned that way rather than discovered. The teams that get hurt here are not the ones that lacked the skills, they are the ones that committed to a date before finding out what they were committing to.

What the Path Actually Looks Like

In order, and the order is the part that matters. Doing these in the wrong sequence is where the schedule goes.

One. Audit the native dependencies. Every library shipping native code, checked against New Architecture support and current maintenance. Cheap, fast, and the only step that can change everything downstream. It goes first for that reason alone.

Two. Move to a development build. Get the team off Expo Go and onto a binary containing your real native dependencies, so that from here on you are testing something shaped like what ships. Everything after this is easier once the thing on the phone matches reality.

Three. Split configuration per build profile. Separate values for development, staging and production, with production ones held somewhere that is not a developer's machine. This is what stops a staging URL reaching a shipped binary.

Four. Set up build profiles and credentials properly, once. Signing, certificates and store credentials in a place the whole team can build from. Tedious, and it is the step that removes the single point of failure.

Five. Wire over-the-air updates with a written policy. Not just the mechanism, but the rules. What may go out this way, who may send it, and how it gets rolled back. A channel that skips review needs a process precisely because it skips review.

Six. First store submission. Budget more time than the code suggests. First submissions are usually delayed by privacy declarations, permission justifications and screenshots rather than anything technical.

An honest admission about that step. We have been caught by it too. Not by the code, which was ready, but by a permission string that described what the app did in language a reviewer read as something else, and by a privacy declaration that took three attempts to get right. The engineering was done a week before the app went live. If your plan treats submission as a formality at the end of the last sprint, move it earlier and give it a name and an owner.

Seven. The boring part. A repeatable release cadence, crash reporting somebody actually reads, and a rehearsed rollback. This is the difference between having shipped once and being able to ship whenever you want to.

We have taken products through this path and through the harder versions of it, including systems where the failure modes are considerably less forgiving than a delayed release. Our team has shipped an exam platform handling over 10 million requests a minute and an EdTech platform serving more than 250,000 daily users, and Pixytan, which tracks over 30,000 vehicles. More than 50 products have gone out of this studio, and we hold Top Rated Plus at 4.9 on Upwork. The reason that is relevant here is narrow and specific: none of the steps above are difficult, and all of them are unfamiliar the first time.

YK
Written by

CEO and co-founder of Geminate Solutions, a software and product development partner. He has led teams shipping custom web apps, mobile apps, SaaS platforms, and AI products that serve over 250,000 daily active users.

Free 48-hour review

Find out what is actually blocking your Expo release.

Send us your app.json and package.json, or the repository if you can share it. A senior mobile engineer audits your native dependencies against the New Architecture, identifies which of the four walls you are on, and sends back the shortest path to a store build within 48 hours. Where the answer is that your team can do this in a sprint, we say so, and there is nothing to buy.

  • Which native dependencies block the New Architecture, named, with maintained replacements where they exist
  • Whether you need a development build, and what has to change to get one
  • Where your configuration is inlined at build time and what that risks shipping
  • The order of operations for your specific app, and what your team can carry unaided

Get your free Expo production readiness review

Drop your work email and a line on where the build is stuck. We reply within 48 hours.

Reply in 48 hours. Free, no pitch, no commitment. By submitting, you agree we may use your details to reply, under our legitimate interest and stored via EmailJS. We never sell your data. Privacy Policy.

FAQ

Frequently asked questions

Why can Expo Go not run your app any more?
Because Expo Go is a pre-built app with a fixed set of native code compiled into it, and your JavaScript is loaded into that fixed container. The moment your product needs a library shipping native code of its own, a payment SDK or a Bluetooth library or background location, there is no mechanism to get that native code into a binary Expo controls and you do not. This is the architecture rather than a bug, so no flag fixes it. The replacement is a development build, which Expo describes as your own version of Expo Go, compiled with your native dependencies included through the expo-dev-client library.
Do you have to eject from Expo?
No, and the command no longer exists. Ejecting was a one-way door that handed you permanent ownership of the native iOS and Android projects. Expo replaced it with Continuous Native Generation, where short-lived native projects are generated on demand from your app configuration by running npx expo prebuild, rather than maintained by hand for the life of the codebase. Managed versus bare workflow is deprecated framing too. If someone is advising you to eject, they are working from documentation that is years out of date, which is worth knowing before you take the rest of their advice.
What is the difference between Expo Go and a development build?
Expo Go is a shared app on the store that anyone can install, carrying a fixed native surface that Expo compiled. A development build is a binary you compile yourself containing exactly the native libraries your project depends on, plus expo-dev-client for developer tooling. Practically, Expo Go is for the first week of a project and for prototypes that never leave the team. A development build is what you use once the app has real native requirements and is heading for the stores. Expo is explicit that Expo Go is not intended for production deployment.
Why did your environment variables not change when you changed them?
Because EXPO_PUBLIC_ variables are inlined into the JavaScript bundle when the build is made, not read at startup. The value was already baked into the bundle at compile time, so editing an env file and restarting an installed app does nothing. Each environment needs its own build, and a wrong value discovered after release reaches users only through a new store submission or, if the change is JavaScript only, an over-the-air update. This is the most common reason a staging API URL ends up inside a shipped production binary.
What does the React Native New Architecture migration actually cost you?
Far less in your own code than most teams expect, and almost all of it in third-party dependencies. The New Architecture replaces the asynchronous bridge with JSI, which lets JavaScript hold direct references to C++ objects, and swaps the legacy renderer for Fabric and legacy native modules for TurboModules. It has been the default since React Native 0.76, after an experimental opt-in from 0.68 in March 2022. Your screens and business logic are usually untouched. The real work is dependency archaeology, finding which native libraries have been updated and which are abandoned. Effort scales with how many native dependencies you have and how well maintained they are, not with the size of your app.
Can you ship a fix without waiting for app store review?
Some fixes. EAS Update serves over-the-air updates to apps using the expo-updates library, covering JavaScript, styling, image assets, copy, translations and screen layouts, including gradual rollouts to a percentage of users. It cannot cover anything that changes the binary. Native code or native dependency changes, newly requested permissions such as camera or location, and Expo SDK upgrades all require a fresh build and a store submission. Knowing which side of that line a hotfix sits on is the difference between shipping in minutes and waiting on review.
Who should not migrate off Expo Go?
Anyone whose app is fully served by what the Expo SDK already provides. If your feature set is screens, navigation, network calls, camera, notifications and storage, and nothing on the roadmap needs a native library Expo has not already wrapped, then Expo Go plus a straightforward build profile is genuinely sufficient. The same goes for internal demo tools, prototypes still being validated, and teams already shipping without friction. There is no prize for adopting more build infrastructure than your product requires.
Does moving an Expo app to production require rewriting it?
No, and this is where teams get sold the most unnecessary work. Your JavaScript, components, navigation and business logic almost all survive the move intact. What changes sits underneath the application code, in how the binary is built, how configuration reaches it, how credentials and signing are handled, and how releases go out. An agency that responds to an Expo prototype by proposing a rebuild from scratch is proposing the engagement that suits them, not the work the codebase needs.
How do you know whether your own team can handle the migration?
Four questions. Has anyone shipped an app to both the App Store and Play Store before, including certificates and provisioning. Can somebody read a native build failure and tell a Gradle problem from a CocoaPods problem. Does a release depend on one specific person's laptop. And has anybody audited your native dependencies against the New Architecture yet. Two or more no answers does not mean you need help forever, it means the first release will take considerably longer than estimated and should be planned that way.
What order should an Expo production migration be done in?
Audit native dependencies first, because it is cheap and it is the only step that can change the plan. Then move to a development build so the team tests what will actually ship. Then split configuration per build profile so staging values cannot reach a production bundle. Then set up build profiles and store credentials properly, once, somewhere that is not a laptop. Then wire over-the-air updates with an explicit policy about what may go out that way. Then the first store submission, which runs long because of review metadata rather than code. Doing the dependency audit last is the most expensive sequencing mistake available.
Is Geminate Solutions a staffing agency?
No. Geminate Solutions is a software and product development partner. You get a team that takes delivery of the work and is answerable for whether it ships, rather than a developer rented by the hour. On this topic the distinction matters more than usual, because the honest answer to an Expo migration question is often that your existing team can do it in a sprint given the right order of operations, and a partner paid to deliver an outcome has no reason to tell you otherwise. You own the code, the store accounts and the build pipeline either way.
FREE WEBSITE REVIEW

Get a free 24-hour review of your website

Send us your website link on WhatsApp. Within 24 hours we tell you exactly what is costing you customers and what we would fix first. No obligation and no sales script.

Send my website for review

4.9 rated Ā· 50+ products shipped Ā· 250K+ daily users served

GET STARTED

Already built something, and it is starting to break?

Most teams that reach us have a working product and a growing list of things that scare them. We read the code first and tell you what actually needs fixing, including the parts that do not. Rebuilding from scratch is rarely the honest answer.

Related Articles