Expo SDK 54's precompiled XCFrameworks cut our iOS build from 14 min to 4
Our fitness kit's iOS build used to take 14 minutes on CI. After upgrading to Expo SDK 54, it takes 4.
That's not a CI optimisation. We didn't add parallel runners or bump the machine size. We just landed on the SDK that ships precompiled XCFrameworks by default. Same code, same build profile, same EAS plan — 70% time reduction.
Worth understanding what Expo actually changed.
What got precompiled
Historically, every iOS Expo build had to compile the entire React Native core and all native modules from source. Hermes, the Reanimated worklets runtime, the Skia engine, JSI bridges — every one of them rebuilt from scratch on every CI run unless cached.
SDK 54 ships precompiled XCFrameworks: the native core (React Native 0.81, Hermes, the standard module set) arrives as a binary. CI no longer compiles them — it links them.
For our setup, that meant the iOS build's "compile sources" step went from ~11 minutes to ~90 seconds. The remaining time is what you'd expect: code signing, prebuild, asset processing, your own Swift code (we have a small amount). Nothing to optimise further until we start writing more native code.
Two facts to note:
SDK 54 is built on React Native 0.81 + React 19.1. If your app has direct RN imports, this is a real upgrade — useTransition, server-component-aware hooks, the new error overlay all show up. Most kit-using teams won't touch this surface, but if you've been holding back on React 19 because of the Expo lag, the lag is gone.
SDK 54 is the last SDK to support the Legacy Architecture. The Legacy Architecture was frozen in June 2025 — no new features, no bugfixes. SDK 55 won't ship it. If your kit still depends on a module that hasn't moved to the New Architecture, you have until SDK 55 to migrate.
EAS Update got cheaper too
The other unsung change in 54: Hermes bytecode diffing for EAS Update is now the default.
Before: every JS update was a full bundle download. A small TypeScript change shipping to 1000 active devices meant 1000 × (full bundle size) bytes over the network. With our fitness kit's bundle at ~3.8 MB, that was 3.8 GB of bandwidth per update — bandwidth that counted against our EAS plan.
With bytecode diffing on, the client downloads a binary patch against its last installed bytecode. Same kind of delta-encoded update Chrome and iOS itself use for application updates. Our last three updates averaged 180 KB each — a 20× reduction. We're now sending the same number of updates per month on the same plan we were going to outgrow.
The practical effect: shipping a typo fix to live users went from "should we batch this with the next feature?" to "ship it." We pushed seven updates last week. Six of them were single-file fixes that would have been queued under the old economics.
How we wired it
The setup is two flags and a workflow. Our eas.json:
{
"cli": { "version": ">= 18.10.0" },
"build": {
"production": {
"channel": "production",
"ios": { "simulator": false },
"android": { "buildType": "app-bundle" }
},
"preview": {
"channel": "preview",
"ios": { "simulator": false }
}
},
"submit": {
"production": {
"ios": { "appleId": "$APPLE_ID", "ascAppId": "$ASC_APP_ID" },
"android": { "serviceAccountKeyPath": "./google-service-account.json" }
}
}
}The actual deploy is one command from CI:
# JS-only update — ships to existing installs in ~30 seconds
eas update --channel production --message "$(git log -1 --pretty=%s)"
# Native change → new binary
eas build --platform all --profile production --auto-submitThe GitHub Action wires the path filter so JS-only changes route to eas update and native changes route to eas build. The Action is in our fitness kit's repo — buyers get it pre-wired.
What we're betting on
Expo's roadmap points at SDK 55 dropping the Legacy Architecture entirely and shipping more of the toolchain as precompiled artifacts. We're going to ride that wave — every SDK upgrade should make our kits' CI faster, not slower.
The broader bet: production deploy speed is the metric that ends up mattering most. A user who can ship a fix in 30 seconds tries more things. A user who has to wait 14 minutes for a build batches changes, ships less often, and ends up shipping more risk per push.
This is why our kits ship deploy scripts, EAS workflows, and CI configs from day one — not "set up your own pipeline, the README has hints." The kit isn't done until the deploy is one command.
SDK 54 didn't change that bar. It just made the bar cheaper to clear.