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 is not a CI optimisation. We did not add parallel runners or bump the machine size. We landed on the SDK that ships the native core as precompiled binaries, and the compile step that used to dominate the build nearly vanished. Same code, same build profile, same EAS plan — about 70% off the clock.
Two caveats before the details, because this post makes a numbers claim. First, 14 minutes to 4 is our measurement on our app and our CI — keep it read as labeled first-person experience, not as a promise for your project. Second, Expo itself models this honesty: the SDK 54 changelog quotes a dramatic framework-level improvement (a clean RNTester build dropping from 120 seconds to 10 on an M4 Max) and then explicitly warns that your own app is unlikely to see anything near a much faster gain. This post follows the same rule. Worth understanding what Expo actually changed, and what part of it transfers to you.
What got precompiled
Historically, every iOS Expo build compiled the entire React Native core and its native modules from source. Hermes, the Reanimated worklets runtime, the Skia engine, the JSI bridges — rebuilt from scratch on every CI run unless caching saved you. Caching helped, until it did not: a cache miss on a Monday morning meant the full 14 minutes, and cache invalidation had a habit of arriving exactly when you were trying to ship.
SDK 54 changed the default. Starting with React Native 0.81 and SDK 54, React Native on iOS ships as precompiled XCFrameworks — the native core arrives as a binary, and CI links it instead of compiling it. That is the whole mechanism, and it is directly citable from the changelog. For our setup, the iOS build's "compile sources" step went from roughly 11 minutes to about 90 seconds. The remaining time is what you would expect: code signing, prebuild, asset processing, and our own small amount of Swift code. There is nothing left to optimise until we start writing more native code — which is exactly the position you want your CI to be in.
On versions: the changelog pins SDK 54 to React Native 0.81, paired with the React 19 line, so the React 19 feature set — transitions, the reworked error overlay, server-component-aware hooks — shows up with the upgrade. Most kit-using teams will never touch that surface directly, but if you have been holding back on React 19 while waiting for the Expo lag to close, the lag is closed. One planning note alongside it: Expo has been steering the ecosystem toward the New Architecture release by release, so before you plan a quarter around a module that still needs the legacy stack, check the current changelog for the support timeline rather than trusting any blog post's memory of it — including this one's.
EAS Update got cheaper too
The other unsung change in SDK 54 sits in the update layer. EAS Update is Expo's cloud service for shipping JavaScript updates to existing installs without a store resubmission, and SDK 54 made Hermes bytecode diffing the default behavior for those updates.
Before: every JavaScript update was a full bundle download. A small TypeScript change shipping to a thousand active devices meant a thousand full-bundle downloads. With our fitness kit's bundle at roughly 3.8 MB, one update cost on the order of 3.8 GB of bandwidth — bandwidth counted against the EAS plan, and the reason teams batch small fixes instead of shipping them.
With bytecode diffing on, the client downloads a binary patch against its last installed bytecode — the same delta-encoding idea Chrome and iOS themselves use for application updates. Our last three updates averaged around 180 KB each, roughly a 20x reduction on our bundle. We now ship the same number of updates per month on the plan we were about to outgrow.
The practical effect is cultural, not just financial. Shipping a typo fix to live users went from "should we batch this with the next feature" to "ship it." We pushed seven updates in one week recently, and six of them were single-file fixes that would have queued for days under the old economics. Small diffs, shipped fast, each carrying almost no risk — that is the deployment posture every team claims to want, and it took a defaults change in the toolchain to make it the path of least resistance.
One codebase. iOS, Android, and web.
The Fitness Kit ships with auth, a database, and a backend already connected — no setup. Live demo at fitness-preview.otf-kit.dev.
How we wired it
The setup is a build profile, a channel discipline, and one CI rule: JavaScript-only changes route to eas update, native changes route to eas build. 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 fast
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 the two commands pick the right lane automatically. That Action ships pre-wired in our fitness kit — buyers get the pipeline, not hints about building one. This is the same one-command-deploy thesis behind our ship-to-production checklist: the kit is not done until the deploy is a single command a tired human (or an agent) can run correctly at midnight.
What this means for your CI budget
Put the two changes together and the economics of a React Native team shift. Build minutes stop being the binding constraint on iOS iteration, and update bandwidth stops being the reason small fixes wait. Concretely: measure your own "compile sources" step before and after the SDK 54 bump and put the delta in the upgrade PR — it is the number that justifies every future SDK upgrade to whoever approves CI spend. Then confirm the win stuck by watching production, which is where Sentry error tracking for React Native earns its place: faster shipping means nothing if a bad update reaches users silently. And if the SDK 54 bump has you thinking about structural use rather than one-off wins, the single-codebase strategy is the companion read — one component system across iOS, Android, and web means each Expo release is a version bump, not a project.
What we are betting on
The broader bet: production deploy speed is the metric that ends up mattering most. A team that can ship a fix in seconds tries more things. A team that waits 14 minutes for a build batches changes, ships less often, and packs more risk into every push. Later SDKs continue down the precompilation line Expo opened with 54 — check the current changelog for exactly how far it has gone — and we intend to ride that wave so every SDK upgrade makes kit CI faster, not slower.
That is also why kits ship deploy scripts, EAS workflows, and CI configs from day one instead of "set up your own pipeline, the README has hints." SDK 54 did not change that bar. It just made the bar cheaper to clear — and if you want the bar pre-cleared, OTF kits ship the build profiles, update channels, and CI wiring as part of the starting point, so your coding agent inherits a deploy pipeline instead of inventing one.
Sources
- Expo SDK 54 changelog — precompiled XCFrameworks on iOS starting with React Native 0.81, RNTester 120s to 10s on M4 Max with Expo's own caveat, Hermes bytecode diffing for EAS Update.
- EAS Update introduction — EAS Update as Expo's cloud service for shipping updates to existing installs.
- OTF kits — full-stack kits shipping deploy scripts, EAS workflows, and CI configs from day one.
Stop wiring. Start shipping.
- Login, database, and backend already connected — nothing to set up
- iOS + Android + web from one codebase
- AI configs pre-tuned + 40+ tested prompts included