How to update an Expo app to the latest SDK 57 packages without breaking the build
A repeatable checklist for updating Expo SDK 57 apps: expo-doctor, expo install --fix, stale native folders, patch-package pitfalls, and the verification steps that catch breakage before your users do.
September 14, 2026 · Thomino
Every few weeks Expo publishes new patch releases for SDK 57, and every few weeks npx expo-doctor starts telling you that twenty packages are out of date. Updating is usually a one-liner. Sometimes it takes down your iOS build for a day.
This is the process I use to update 21 production React Native templates in a single pass. It's written so you can run it on one app. The order matters: each step catches a class of problem that the next step would otherwise hide.
1. Start from a clean tree
Commit or stash everything first. The update touches package.json, the lockfile and possibly native config, and you want the diff to contain only the update. Check that git status is clean before you start.
2. Run expo-doctor and read the whole output
On a healthy app you get "21/21 checks passed". On an app that's behind you'll see one failure:
Read the rest of the output too. If there's a second failure, note it now. In our batch one app also failed Check Expo config for common issues, and it was a pre-existing problem that had nothing to do with versions (more on that below).
3. Let Expo pick the versions
This bumps every Expo-managed package to the version Expo has tested against your SDK. Don't hand-edit versions and don't run npm update, which ignores Expo's compatibility table. After it finishes, run npx expo-doctor again. The package check should pass.
4. Check whether your patches survived
If your project uses patch-package, the install output will tell you whether each patch still applies:
A patch is tied to the exact version it was written for. When the version bumps, one of three things is true:
- The fix landed upstream. Delete the patch, remove the
postinstallscript and thepatch-packagedev dependency if nothing else needs them, and runnpm installto sync the lockfile. - The fix is still needed and still applies. Regenerate it against the new version with
npx patch-package <package>after re-applying your change by hand. - The bug moved. The old fix is obsolete but a new one is required. This is what happened to us with
expo-modules-jsi57.1.0, which is covered in its own post.
Whatever you do, don't leave a failing patch in place. It prints an error on every install and hides real problems.
5. Type-check and bundle
TypeScript catches API changes in the updated packages. The export forces Metro to compile the full bundle, which catches things a dev server won't until you navigate to the broken screen. Both should finish with zero errors. Together they take about a minute.
6. Boot the dev server
Watch the terminal for the first 20 seconds. You're looking for config warnings, plugin errors, and anything printed in yellow or red before "Waiting on http://localhost:8081". If you're scripting this, set CI=1 so Metro doesn't wait for keyboard input, and kill the process group afterwards. Killing only the npx wrapper leaves the Metro child running on its port. I learned that the hard way with 21 orphaned servers.
7. Build the native app
This is the step people skip, and it's the only one that would have caught the Xcode 26 failure. Nothing above compiles Swift or Kotlin.
Use --clean. A stale ios folder from a previous build carries an old Podfile.lock, and after a package update pod install will fail with "could not find compatible versions" or complain about a module you removed months ago. Regenerating the folder takes a minute and removes a whole class of false alarms. The folder is gitignored in a managed project, so nothing is lost.
If the build fails inside a specific pod, that pod's version is where to look. The error is almost always in the first ten lines of red output, not the last.
8. Fix what doctor found in step 2
Now go back to any non-version failure. The one we hit:
The app had a dynamic config that started with const base = require('./app.json').expo. That works at runtime, but doctor only recognises the function form. Changing it to module.exports = ({ config }) => ({ ...config, /* overrides */ }) gives you the same resolved config and a green check. You can prove the output is identical by hashing npx expo config --type public --json before and after.
9. Don't touch the audit fixer's big red button
npm install will report vulnerabilities and suggest npm audit fix --force. Don't. On an Expo project that command resolves the moderate query-string findings by downgrading expo-router by a major version. Plain npm audit fix without the flag is safe. The remaining findings are usually transitive and blocked upstream.
10. Commit with the lockfile
Commit package.json, package-lock.json and any patch changes together. A lockfile that's out of sync with the manifest is the number one reason a teammate's npm ci fails the next morning.
The checklist
Per app this takes ten minutes when nothing is wrong, and it tells you within ten minutes when something is. Our templates go through exactly this pass on every Expo release, which is why the version you download is the one that builds.