Expo SDK 58 Upgrade Guide: React Native 0.88 and iOS 27
SDK 57 was a small, focused release. SDK 58 is not. It skips React Native 0.87 and lands on 0.88, moves iOS onto the UIKit scene-based life cycle because the iOS 27 SDK demands it, turns R8 on for Android release builds, reworks Expo Router's navigation core, and quietly moves Gesture Handler across a major version. Five separate things that can each break an app, in one upgrade. This guide is the order I'd do it in, the changes that actually bite, and the one config plugin break that hits every Expo app using React Native Firebase.
The short version
Run npx expo install expo@next --fix, then npx expo prebuild --clean, then rebuild your development clients. The five things most likely to stop you: the Strict TypeScript API is now the default, so react-native/Libraries/* deep imports are type errors; iOS gets a SceneDelegate.swift and a scene manifest, which breaks config plugins that patch AppDelegate.swift (including React Native Firebase, right now); R8 runs on Android release builds, so anything using reflection needs keep rules; Expo Router removed initialRouteName, redirect, initialParams, and freezeOnBlur; and --fix drags Gesture Handler from 2.x to 3.x without mentioning it. Do it on a branch, with a real regression pass at the end.
SDK 58 is in beta, as of September 21, 2026
The beta opened on 15 September 2026 and Expo said it would run three to four weeks. On npm, expo@latest is still 57.0.24; SDK 58 lives on the next tag, currently 58.0.0-preview.4. It ships React Native 0.88.0-rc.1, because React Native 0.88 itself hasn't shipped stable. Expo will move to the stable React Native release and then ship SDK 58 shortly after. Treat everything below as the upgrade you do now on a branch, not the one you ship to the App Store this week — unless you specifically need iOS 27 support, which is the one good reason to ship on the beta.
Expo SDK 58 at a glance
| Dependency or platform | SDK 58 (beta) | SDK 57 |
|---|---|---|
| React Native | 0.88 (currently 0.88.0-rc.1) | 0.86 |
| React | 19.3.0 | 19.2.3 |
| @types/react | ~19.3.0 | ~19.2.2 |
| Node.js minimum | 22.13, 24.3, or 26+ | 22.13 |
| Expo Router | ~58.0.5 | ~57.0.22 |
| Reanimated | 4.6.0 | 4.5.1 |
| Gesture Handler | ~3.2.1 | ~2.32.0 |
| iOS life cycle | Scene-based (UIScene) |
Application-based |
| Android release minification | R8 on by default | Off by default |
Verify the exact pins for your own project rather than trusting a table in a blog post — including this one. npx expo install --check compares every dependency against the version the installed SDK expects, and the beta's pins move during the beta period.
What actually changed in SDK 58
React Native 0.88, skipping 0.87
SDK 57 was on React Native 0.86. SDK 58 goes straight to 0.88, so you inherit 0.87 and 0.88 in a single upgrade. React Native 0.87 was the cleanup release: it removed a long list of deprecated APIs and made the Strict TypeScript API the default. That is where most of your compile errors will come from.
0.88 itself adds the opt-in npx react-native spm Swift Package Manager pipeline (CocoaPods remains the default and supported path), fontVariationSettings on Text, native ArrayBuffer representations in TurboModules, and WebSocket inspection in React Native DevTools.
iOS 27 and the scene-based life cycle
The iOS 27 SDK requires the UIKit scene life cycle. An app built with Xcode 27 that still uses the application-based life cycle does not launch correctly on iOS 27, so Expo moved. npx expo prebuild now generates a SceneDelegate.swift and a UIApplicationSceneManifest entry in Info.plist, and the UIWindow is created by the scene delegate rather than in didFinishLaunchingWithOptions.
R8 on by default for Android release builds
The Android app template now sets android.enableMinifyInReleaseBuilds=true in gradle.properties. R8 shrinks, optimizes, and obfuscates your Java and Kotlin code with the proguard-android-optimize.txt preset. Smaller app, faster startup, and a new class of release-only crash if a library relies on reflection.
Expo Router's navigation core was rewritten
Navigation state and route keys are deterministic now, dispatch is deferred until after commit, and most of the forked React Navigation API surface is gone. Data loaders, server-side rendering, middleware, native tabs, and toolbars all graduated to stable. Apps that only use Stack, Link, and useRouter need a few small changes; custom navigators and anything importing from expo-router/react-navigation need a proper review.
The quieter wins
- Faster Android builds.
expo-modules-corenow ships precompiled.sofiles for all four ABIs. Expo measured a clean debug build dropping from roughly 80 seconds to 39 on an M3 Pro, and the saving is bigger on CI runners with fewer cores. - Fingerprint defaults to the
balancedpreset. Bumpingversionin app.json or touching files inside node_modules no longer changes your fingerprint, so you stop paying for unnecessary native rebuilds. - Faster Expo modules on iOS. Simple-value calls are cheaper, long strings move up to 3.8× faster, and promises cost less to create and resolve. Nothing to adopt — every Expo module gets it.
- Expo Modules 2.0 beta on both platforms: write a normal Swift or Kotlin class with annotations instead of the DSL.
- Experimental Rust bundling. "Noxcturnal" is an oxc-based transformer behind
experiments.noxcturnalTransformWorker. Expo measured around 2× faster cold bundling. It only activates when you have no custom transform worker and no Babel plugins or presets, which rules out most real projects today. - New APIs worth knowing:
expo-app-intentsfor Siri and Apple Intelligence (alpha), Android home screen widgets inexpo-widgets,CameraView.scanDocumentAsync,File.digest(), variable font support inexpo-font, and lockscreen audio controls with playlists.
How to upgrade SDK 57 to SDK 58
1. Start from a clean branch
You will want to read this diff, and you will probably want to abandon it once before you keep it:
git status # nothing unrelated in the working tree
git switch -c upgrade/expo-sdk-582. Check Node.js
SDK 58 requires Node 22.13 or newer on the 22 line, 24.3 or newer on the 24 line, or Node 26 and later. Odd-numbered releases like 23 and 25 are not supported at all:
node --versionCheck your CI image and your EAS Build profiles too, not just your laptop. A machine on Node 22.11 will fail in a way that looks like an Expo bug.
3. Install the SDK 58 beta
While SDK 58 is in beta, you install from the next tag rather than a version range:
npx expo install expo@next --fix--fix is doing the real work: it pulls React, React Native, every Expo module, and the third-party packages Expo tracks up to the versions SDK 58 expects. Once SDK 58 ships stable, this becomes the familiar npx expo install expo@^58.0.0 --fix.
--fix also moves you to Gesture Handler 3
This is the upgrade item people miss, because it isn't in the SDK 58 release notes at all — it's a pin change. SDK 57 bundled react-native-gesture-handler ~2.32.0; SDK 58 bundles ~3.2.1. So a single --fix quietly crosses a major version of the library your gestures depend on. Gesture Handler 3 replaced the builder API with hooks and changed how gesture relations, callbacks, and touchables behave. If your app does anything beyond a swipe-to-dismiss, read the Gesture Handler 3 migration guide and budget separate time for it. Reanimated moves too, but only 4.5.1 to 4.6.0.
Starting fresh instead
For a new project on the beta, use the next template: npx create-expo-app@latest --template default@next. Worth doing anyway, in a scratch directory — a generated SDK 58 project is the reference you'll diff your own native files against in step 6.
4. Run Expo Doctor
npx expo-doctor@latest
npx expo install --checkFix what these report before you build anything. A native build failure on top of an unresolved dependency mismatch is two problems pretending to be one.
5. Migrate TypeScript to the Strict API
This is the change most likely to produce a wall of red. React Native 0.87 made the Strict TypeScript API the default, so deep imports are now type errors and some types changed shape — most visibly refs, which use instance types such as ViewInstance and TextInputInstance:
// Before: deep import, now a type error
import { Platform } from "react-native/Libraries/Utilities/Platform";
import type { ViewProps } from "react-native/Libraries/Components/View/ViewPropTypes";
// After: the public entry point
import { Platform } from "react-native";
import type { ViewProps, ViewInstance } from "react-native";
// Refs changed shape
const ref = useRef<ViewInstance>(null); // was useRef<View>(null)If that's a bigger job than you want inside an SDK upgrade — and in a large app it usually is — you can defer it. Add the legacy condition to tsconfig.json, keeping "react-native", which expo/tsconfig.base already sets:
// tsconfig.json
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"customConditions": ["react-native", "react-native-legacy-deep-imports"]
}
}This escape hatch has an expiry date
The legacy deep imports condition is removed after React Native 0.88. That means it works for the SDK 58 cycle and then it is gone. Defer the migration if you need to ship, but schedule it inside this cycle rather than discovering it during the SDK 59 upgrade, when you'll also be fighting whatever that release changes.
6. Regenerate your native projects
If you use Continuous Native Generation and don't commit ios and android, this is one command:
npx expo prebuild --cleanThat regenerates both platforms, which is what produces the new SceneDelegate.swift, the UIApplicationSceneManifest entry in Info.plist, and the android.enableMinifyInReleaseBuilds=true line in gradle.properties. If you maintain the ios directory by hand, see the scene life cycle section below — you have three files to change. For bare React Native apps using Expo modules, npx install-expo-modules@latest performs the migration for you.
7. Work through the Expo Router changes
Four removals will hit ordinary apps. First, initialRouteName is gone; the replacement is an unstable_settings export from that stack's layout:
// app/(tabs)/_layout.tsx
import { Stack } from "expo-router";
export const unstable_settings = {
anchor: "index",
};
export default function FeedLayout() {
return <Stack />; // was <Stack initialRouteName="index" />
}anchor is the back destination when someone deep links into the stack — it is not how you choose the app's first screen. The launch URL decides that, and / resolves to your index.tsx.
Second, layout Screen components no longer accept redirect or initialParams. Render a Redirect from the route file, and read defaults where you read the params:
// app/legacy.tsx
import { Redirect } from "expo-router";
export default function LegacyRoute() {
return <Redirect href="/replacement" />;
}
// app/feed.tsx โ replaces initialParams
const { sort = "latest" } = useLocalSearchParams<{ sort?: string }>();Third, tabs and drawers now show only the screens declared in the layout. If your tab bar quietly lost entries after upgrading, this is why:
// app/(tabs)/_layout.tsx
<Tabs>
<Tabs.Screen name="index" />
<Tabs.Screen name="feed" />
<Tabs.Screen name="hidden" options={{ href: null }} />
</Tabs>Fourth, freezeOnBlur has no effect. Remove it and use activityEnabled, which preserves screen state while cleaning up effects:
<Stack>
{/* Stack default: hides content once two screens sit above it */}
<Stack.Screen name="feed" activityEnabled />
{/* Hides content as soon as another screen is focused */}
<Stack.Screen name="account" activityEnabled={1} />
</Stack>Two more to check. Imperative navigation with navigation.navigate("(tabs)", { screen: "feed" }) no longer builds nested state — screen, params, and initial are ordinary user params now, so navigate to a complete href with router.push({ pathname: "/(tabs)/feed/[id]", params: { id: "42" } }). And expo-symbols and @expo/ui became optional peer dependencies, so if you use Android md icons in native tabs or Stack.Toolbar, install them explicitly:
npx expo install expo-symbols # Android md icons in native tabs
npx expo install @expo/ui # Android Stack.ToolbarProtected routes mostly survive untouched. They stay registered in their navigator, and a route whose guard fails renders a redirect instead of being removed from the tree — so the patterns in Expo Router Firebase Auth: protected routes, tabs, and login flows still hold. Set redirectTo on Protected only when you want to control where a failed guard sends people.
8. Rebuild, then actually test
npx expo run:ios
npx expo run:android
# or: eas build --profile development --platform allThen read the diff properly — git diff across package.json, app.json, and the native directories if you commit them. Two upgrades in one branch is how you end up unable to tell which one broke the build.
React Native API removals you will actually hit
Crossing 0.87 and 0.88 together means the removal list is longer than usual. These are the ones that show up in real codebases:
InteractionManageris removed. UserequestIdleCallback. Most uses in app code are arunAfterInteractionswrapper around navigation-adjacent work, and plenty of them can just go.- The
Touchableroot export is removed. If you're still on it, you're several years pastPressable. - The
NativeMethodstypes are removed — part of the same Strict API change as the ref instance types. Modal'sanimatedprop is removed. UseanimationType.StatusBar's deprecatedbackgroundColor,translucent, andnetworkActivityIndicatorVisibleprops are removed. This one catches people, because Android status bar colouring is exactly the sort of thing written once in 2021 and never touched since.ImageBackgroundis deprecated in favour of aViewwith an absolutely positionedImage. Deprecated, not removed — but it's on the clock.
A few Expo-side behaviour changes worth grepping for as well: @expo/ui's <Host> top-aligns on iOS instead of centering, HStack and VStack use system default spacing unless you pass spacing={0}, RNHostView dropped style on Android in favour of modifiers, and expo-localization no longer force-enables RTL from the device locale on iOS — layout direction follows I18nManager now. If you built screens with Expo UI, re-screenshot them.
iOS 27 and the scene life cycle
Under the old application-based life cycle, AppDelegate created the UIWindow and received every URL, user activity, and foreground/background callback. Under the scene life cycle, UIKit creates a UIWindowScene and calls a scene delegate instead, and it no longer calls application(_:open:options:), application(_:continue:restorationHandler:), the four applicationDid/Will foreground and background methods, or the quick action handler.
Expo provides the infrastructure: ExpoAppSceneDelegate creates the window and starts React Native, then routes URL, user activity, Handoff, life cycle, and quick action events back through ExpoAppDelegate, which forwards them to your overrides and to ExpoAppDelegateSubscriber implementations. Delivery stays single, so a legacy override that calls RCTLinkingManager itself won't produce a duplicate url event.
You don't need to do anything if you use CNG and don't commit ios, or if your native code lives in config plugins using ExpoAppDelegateSubscriber or in Expo modules. You do need to act if you maintain ios by hand, have a bare React Native app, or override UIApplicationDelegate methods directly.
For a hand-managed project, there are three changes. Add SceneDelegate.swift next to AppDelegate.swift, in the app target:
// ios/YourApp/SceneDelegate.swift
internal import Expo
@objc(SceneDelegate)
class SceneDelegate: ExpoAppSceneDelegate {
// Extension point for config plugins.
}Declare the scene manifest in Info.plist, keeping the $(PRODUCT_MODULE_NAME) prefix so UISceneDelegateClassName resolves:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>And stop creating the window in AppDelegate. The fastest way to get this right is to run npx expo prebuild --platform ios in a scratch copy of the project and diff the generated ios directory against yours.
Orientation locks changed on iOS 27
iOS 27 makes iPhone apps resizable. requireFullScreen no longer opts you out of resizing, and ScreenOrientation.lockAsync may have no effect while the app is resizable. If your app assumes portrait, that assumption is now advisory. The same resizable-window work is what makes foldables behave — see iPhone Duo support in Expo and React Native for the layout side of it. On the brighter side, expo-glass-effect reports isLiquidGlassAvailable as true in apps built with the iOS 27 SDK, which makes Liquid Glass UI a lot less conditional.
EAS Build has no Xcode 27 image yet
As of 21 September 2026, the latest and sdk-57 EAS Build iOS image is macos-tahoe-26.5-xcode-26.6. Expo has said Xcode 27 and SDK 58 toolchain images are coming soon. Until one lands, an EAS build of your SDK 58 app is compiled against the iOS 26 SDK — fine for testing the JavaScript and most native changes, but it is not an iOS 27 build. If you need one now, build locally with Xcode 27.
The React Native Firebase break
This one is worth its own section, because it is silent, it is iOS-only, and it produces an app that launches fine and then fails at the first Firebase call.
The @react-native-firebase/app config plugin patches AppDelegate.swift to insert FirebaseApp.configure(). It finds its insertion point by matching on self.moduleName = "..." or factory.startReactNative(. Neither exists in the SDK 58 AppDelegate, because React Native is started from the scene delegate now. So prebuild prints this and moves on:
ยป ios: @react-native-firebase/app: Unable to determine correct Firebase
insertion point in AppDelegate.swift. Skipping Firebase addition.import FirebaseCore may still be added, but FirebaseApp.configure() is not, so native Firebase never gets a default app. It is tracked as issue #9331, open as of 21 September 2026. Until it's fixed, a local config plugin that runs after the Firebase one will do it:
// plugins/withFirebaseSceneFix.js
const { withAppDelegate } = require("expo/config-plugins");
const ANCHOR =
"return super.application(application, didFinishLaunchingWithOptions: launchOptions)";
module.exports = function withFirebaseSceneFix(config) {
return withAppDelegate(config, (config) => {
if (config.modResults.language !== "swift") {
throw new Error("withFirebaseSceneFix expects the Swift AppDelegate from SDK 58.");
}
let contents = config.modResults.contents;
// The Firebase plugin may have succeeded after an upgrade โ don't double-insert.
if (contents.includes("FirebaseApp.configure()")) return config;
if (!contents.includes("import FirebaseCore")) {
contents = contents.replace(/^(internal import Expo|import Expo)$/m, "$1\nimport FirebaseCore");
}
if (!contents.includes(ANCHOR)) {
throw new Error("withFirebaseSceneFix: scene-lifecycle return not found in AppDelegate.swift.");
}
config.modResults.contents = contents.replace(ANCHOR, `FirebaseApp.configure()\n\n ${ANCHOR}`);
return config;
});
};Register it after the Firebase plugin, because plugins run in array order:
// app.json
{
"expo": {
"plugins": [
"@react-native-firebase/app",
"./plugins/withFirebaseSceneFix"
]
}
}Both throws are deliberate. A plugin that silently does nothing is exactly the failure mode you're working around — you want prebuild to stop if the AppDelegate shape changes again. Delete the whole thing once the upstream fix ships; the early return means it's harmless in the meantime, but dead config plugins have a way of becoming permanent.
Only applies to native Firebase
If you use the Firebase JS SDK — the approach in Firebase Auth in Expo React Native and most guides on this site — none of this affects you. There is no native Firebase app to configure, no config plugin patching your AppDelegate, and no prebuild step to break. It's a decent argument for the JS SDK on any project that doesn't specifically need native Firebase features.
R8 is on now, and it will find your reflection
Release builds minify by default in SDK 58. The failure mode is specific and nasty: debug builds work, release builds crash or silently misbehave, and the stack trace is obfuscated. Libraries that rely on reflection — JSON serializers mapping to model classes, anything resolving classes by name — need keep rules.
Add them through expo-build-properties rather than editing proguard-rules.pro by hand, so they survive the next prebuild:
// app.json
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": {
"extraProguardRules": "-keep class com.example.models.** { *; }\n-keepattributes Signature,*Annotation*"
}
}
]
]
}
}If a release build breaks and you need to ship before you can diagnose it, turn minification off to confirm R8 is the cause, then turn it back on and fix it properly:
// app.json, inside "plugins" โ temporary, to isolate the problem
[
"expo-build-properties",
{
"android": {
"enableMinifyInReleaseBuilds": false
}
}
]The point of the upgrade is the smaller, faster app you get with R8 on. Leaving it off permanently gives back real startup time. Test a release build on a device before you conclude the upgrade went fine — a development build proves nothing here.
The NODE_ENV change that will confuse your CI
Every Expo CLI command now sets NODE_ENV to development or production before loading .env files and your app config, so a dynamic app.config.js and the .env cascade always agree about which mode they're in. Previously, commands inherited NODE_ENV if one was set, and expo config never set one at all.
So NODE_ENV=test npx expo start used to load .env.test. It now ignores the inherited value and loads .env.development. If a CI job or a shell profile sets NODE_ENV, the environment your app builds against just changed underneath you. Jest still sets NODE_ENV=test in tests, and @expo/env still resolves .env.test.local, .env.test, and .env there, so test runs are unaffected.
Grep your CI config and your package.json scripts for NODE_ENV before you trust a build. If you keep Firebase config in environment variables, the EAS Build environment variables guide covers which values belong in .env files and which belong in EAS environments — this change makes the distinction matter more, not less.
Should you upgrade now?
Three honest answers, depending on who you are:
- You need iOS 27 support now. Upgrade. Expo explicitly says you can ship on the beta for this reason, and it's the only supported way to get the scene life cycle without hand-patching SDK 57. Build locally with Xcode 27 until the EAS image lands.
- You have a live app and no iOS 27 deadline. Do the upgrade on a branch this week, find your problems, then sit on it until stable. The beta runs three to four weeks from 15 September and pins move during it. The value of going early is knowing what's ahead of you, not shipping it.
- You're still on SDK 55 or 56. Go to 57 first. SDK 58 is a big enough release on its own without stacking another SDK's breaking changes underneath it — start with the SDK 57 upgrade guide and come back.
One thing not to do: don't run this upgrade in the same branch as a feature. When something breaks in four weeks you want git log to give you one commit to interrogate.
Does Expo Go support SDK 58?
During the beta, yes — but not from the app stores. Expo Go for SDK 58 is available through Expo CLI for Android devices, Android emulators, and iOS simulators, and through eas go for physical iOS devices. The App Store and Google Play builds update to SDK 58 shortly after the stable release, which is also when they stop supporting SDK 57.
That deadline is the practical reason not to leave this upgrade too long if you rely on Expo Go for development. For production apps, development builds are the right answer regardless: they support your actual native dependencies, and EAS keeps backwards compatibility for older SDKs far longer than Expo Go does.
Production migration checklist
- Upgraded on a dedicated branch with no unrelated changes
- Node 22.13+, 24.3+, or 26+ on every machine, including CI and EAS profiles
npx expo-doctor@latestandnpx expo install --checkboth clean- TypeScript either migrated to the Strict API or explicitly deferred, with a ticket to do it this cycle
- Gesture-driven screens retested after the Gesture Handler 2 to 3 jump
- Native projects regenerated with
npx expo prebuild --clean - Verified
FirebaseApp.configure()is present in the generated AppDelegate.swift, if you use native Firebase - Every config plugin that patches AppDelegate re-verified against the scene-based file
- Deep links, universal links, Handoff, and quick actions tested on iOS under the new life cycle
- Tested an Android release build on a device with R8 enabled
- Expo Router:
initialRouteName,redirect,initialParams, andfreezeOnBlurall removed - Every tab and drawer screen explicitly declared in its layout
- Checked
NODE_ENVis not set by CI or a shell profile in a way that changes which .env loads - Orientation and resizable-window behaviour checked on iOS 27
- EAS Update runtime versions bumped so SDK 58 JavaScript can't reach SDK 57 builds
- Staged rollout with crash and performance monitoring before a full release
Expo SDK 58 FAQ
Which React Native version does Expo SDK 58 use?
React Native 0.88. The beta ships the release candidate, currently 0.88.0-rc.1, because React Native 0.88 has not shipped stable. SDK 57 used 0.86, so SDK 58 picks up both 0.87 and 0.88 at once.
Which React version does Expo SDK 58 use?
React 19.3.0, with @types/react 19.3 and TypeScript 6.0 in the default template. SDK 57 used React 19.2.3.
Is Expo SDK 58 stable yet?
Not as of 21 September 2026. The beta opened on 15 September and Expo said it would run three to four weeks. expo@latest on npm is still SDK 57; SDK 58 is on the next tag. Expo moves to stable once React Native 0.88 ships.
Is SDK 58 a big breaking upgrade?
The biggest in several cycles. Two React Native versions, a removals-heavy 0.87, the Strict TypeScript API by default, the iOS scene life cycle, R8 on for Android release builds, and an Expo Router core rewrite. Budget real time rather than an afternoon.
Can an OTA update move an installed SDK 57 app to SDK 58?
No. SDK 58 has a different React Native runtime and different native code, so it needs a new binary. Keep EAS Update runtime versions aligned so SDK 58 JavaScript is never served to an installed SDK 57 build. The EAS Update vs Remote Config guide covers where that boundary sits.
Can I build an SDK 58 iOS app on EAS Build today?
You can build, but not against the iOS 27 SDK. As of 21 September 2026 the latest EAS Build iOS image ships Xcode 26.6. Expo has said Xcode 27 images are coming soon. Build locally with Xcode 27 if you need a genuine iOS 27 binary now.
Do I need to change anything for the iOS scene life cycle?
Not if you use Continuous Native Generation and keep custom native code in config plugins or Expo modules — npx expo prebuild --clean handles it. You do need to act if you maintain ios by hand, have a bare React Native app, or override UIApplicationDelegate methods directly.
My tab bar lost tabs after upgrading. Why?
SDK 58 shows only the screens you declare in the navigator. Add a <Tabs.Screen name="..." /> for every route that should appear, and options={{ href: null }} for the ones that shouldn't.
Should I install Expo CLI globally?
No. Use the project CLI through npx expo. For EAS, npx eas-cli@latest avoids depending on a stale global install.
Related Expo guides
- Expo SDK 57 upgrade guide: React Native 0.86 migration
- iPhone Duo support in Expo and React Native
- Build Liquid Glass UI with expo-glass-effect
- Expo Router Firebase Auth: protected routes and login flows
- EAS Build and Submit checklist for Expo React Native
- React Native Gesture Handler migration guide
Need this built for you? I take on contract Expo and React Native work — see how I can help.