← Back to portfolio
Case study

Upgrading a live Expo app from SDK 53 to 57 in one jump

App: Animal Adventures, a bilingual (EN/ES) educational app for preschoolers. It's live on Google Play in the Families program and built with React Native + Expo. I designed, built and published it solo.

Upgrade: Expo SDK 53 → 57 · React Native 0.79.5 → 0.86.2 · React 19.0.0 → 19.2.3

When: Main upgrade Aug 26, 2026, merged the next day. Follow-up fixes Sep 4 and Sep 21.

Time spent: ~16 hours over 2 days for the upgrade, 1 day of full testing, then released to Google Play

Why it couldn't wait

Google Play started requiring targetSdk 36 for app updates on Aug 31, 2026. My gradle.properties pinned targetSdkVersion=35, which would have blocked every future update. Fixing that properly meant moving to a current Expo SDK. Since React Native 0.82, that also means the New Architecture: the old architecture no longer exists.

So this was an architecture migration with a deadline, not a routine dependency bump.

What I did

1. Jumped four SDK versions at once (53 → 57)

I did it in one pass instead of four separate upgrades, on its own branch (upgrade/expo-sdk-57), merged the next day as a single PR.

2. Migrated from the old to the New Architecture

The checked-in MainApplication.kt was still force-disabling bridgeless mode, TurboModules and Fabric. Under SDK 57 that would have broken the app.

3. Switched to Continuous Native Generation (CNG)

Instead of hand-patching old native code, I deleted the checked-in android/ and ios/ folders and let Expo Prebuild regenerate them from config. This removed the stale flags and makes future upgrades much cheaper.

What broke (or would have)

  • An undeclared dependency. Three components used @expo/vector-icons, but it was never listed in package.json. It worked on SDK 53 only because it came bundled with expo. On SDK 57 it doesn't, so the build would have failed. Fix: declared it explicitly.
  • A duplicate export. constants/index.js exported COLORS twice. Older tooling tolerated it and the new toolchain wouldn't. Fix: removed the duplicate.
  • Dead weight. I removed @react-navigation/*, styled-components, expo-av, react-native-gesture-handler, react-native-screens and unused Babel aliases, after checking that each had zero imports. Fewer packages means fewer things that can break the next upgrade.

After the merge

  • Sep 4: expo-doctor flagged six packages behind their expected patch versions. I aligned them.
  • Sep 21: local iOS builds broke on Xcode 26.2. Swift 6.2.3 rejected some Swift concurrency attributes in expo-modules-jsi, and SDK 57 officially wants Xcode 26.4+. I didn't want to wait for an upstream fix or downgrade Xcode, so I patched the installed package with patch-package, set to apply automatically on postinstall.

Result

  • The app is on the current SDK and the New Architecture, and meets Google Play's targetSdk 36 rule for updates.
  • The native folders are now generated, not hand-maintained, so the next upgrade is a config change instead of a native-code excavation.
  • Upgrade to production in about 4 days: ~2 days of upgrade work, 1 day of full testing, and the release to Google Play the day after.