March 19, 2026·10 min read
OTA updates with Expo: what you can actually patch
OTA is magical until someone tries to ship a new native module through it. Know the boundary: JS and assets yes; native code no. Channels let you stage like you wish git branches did for users. Invest in rollback and messaging before you need them at 2 a.m.
Runtime compatibility
Your update must match the native runtime on the device. Track runtime version and refuse incompatible bundles with a clear message.
Test OTA against the oldest build you still support.
Rollback story
If a JS deploy breaks login, you need a way to publish a fix fast—or instruct users to reinstall. Runbooks beat heroics at 2 a.m.
Keep migrations backward compatible when possible.
User trust
Tell users when you’re updating silently vs asking them to update from the store for big changes.
Large asset downloads on cellular need care; respect data saver modes where you can.
Release checklist for OTA
Before you promote a bundle: confirm no native API assumptions snuck in, run smoke tests on the minimum supported binary, and verify channel targeting (staging vs production). After deploy: watch error rates and session starts for an hour—rollback first, debug second if login or payments regressed.
Document who can publish updates and require two-person review for production channels if you can.
Compatibility matrices
JS bundles must match native runtime capabilities. Track runtime version and block incompatible updates with clear in-app messaging. Test OTA against oldest supported binaries—users lag store adoption. Document which native modules require store builds versus JS-only fixes. When Hermes or bridge internals change, assume OTA risk until proven otherwise.
Channel strategy
Use staging channels for internal QA, production for general availability, and hotfix channels when you must bypass normal QA in emergencies—sparingly. Automate promotion rules; manual mistakes publish wrong bundles. Tag releases in git matching channel publishes for traceability. Monitor adoption per channel via analytics to ensure rollouts match intent.
Rollback and forward-fix playbooks
Define who can publish rollbacks, how to validate them, and how to communicate to users if behavior changed visibly. Sometimes forward-fixing with another OTA is faster than rollback—decide criteria ahead of time. Keep a list of ‘break-glass’ contacts for weekends. Practice a dry-run quarterly; panic without practice wastes minutes that matter.
Asset size and network empathy
Large assets over cellular annoy users and burn data caps. Compress images, split optional assets, and respect system data saver settings when possible. Show progress for big downloads. Consider deferring non-critical assets until after first interactive screen.
Security and integrity
Verify update signatures per Expo’s guidance. Protect publish tokens like production credentials—leaked tokens mean arbitrary code execution on clients. Rotate keys after personnel changes. Review CI pipelines that publish updates for injection risks.
Communications and transparency
Users rarely care about OTA mechanics until something breaks. When silent updates change behavior materially, consider release notes in-app or on the web. Support should know how to identify build and bundle versions from user reports. Align messaging with marketing to avoid promising store features that arrived via OTA without store review nuances—follow platform rules.
Operational metrics
Track update success rates, crash-free sessions post-update, time-to-download, and fallback rates to store builds. Alert on anomalies. Use staged rollouts when available. Learn from incidents and update runbooks—OTA velocity is a superpower that demands discipline.
Shipping and reliability habits (1)
Security and privacy expectations move faster than roadmaps. Treat analytics, crash, and attribution SDKs as part of your threat model: initialize them deliberately, document data flows, and verify ‘off’ truly stops network calls. Client-side secrets are public secrets—anything shipped in an APK or IPA should be assumed extractable. Pair mobile changes with backend policies so authorization remains consistent across platforms.
Accessibility is compatibility. Labels, focus order, and dynamic type are not polish—they determine whether users can complete tasks at all. Test with VoiceOver and TalkBack on hardware; simulators miss focus bugs. When designs prioritize minimalism, negotiate text alternatives for icon-only controls. Accessibility regressions often follow navigation redesigns—add checklist items to those PRs specifically.
Platform differences worth rehearsing (2)
Error boundaries catch render failures, not native crashes or async mistakes. Pair them with platform crash reporting and structured client logs. Fallback UI should include build identifiers and humane copy—never raw stack traces for end users. Test fallbacks with screen readers; a broken error screen is still broken UX.
Storage is not a database. AsyncStorage and MMKV excel at key-value preferences; SQLite or remote APIs belong elsewhere for relational data. Migrations should be incremental, logged, and non-blocking for UI. Secure tokens need secure storage when your model demands it—speed is not a substitute for correctness on auth material.
Security, privacy, and data handling (3)
Splash screens and launch gates should reflect honest readiness: fonts, theme, session, critical remote config—not every SDK under the sun. Infinite splash is worse than a slightly longer branded hold. Match native and JS background colors to avoid flashes; respect reduced motion for animations.
JWT and session refresh flows need single-flight refresh, clear logout semantics, and secure storage for refresh tokens when appropriate. Parallel 401s should not stampede refresh endpoints. Clock skew and biometrics policies belong in explicit product decisions, not accidental implementation details.
Performance and measurement discipline (4)
Testing onboarding changes with funnel metrics beats debating opinions. Segment by acquisition channel and platform; back behavior differs. Skip paths must be genuine—dark patterns may win short metrics and destroy brand trust. Localization length tests prevent clipped CTAs in verbose languages.
Background execution policies change with OS updates—revalidate after major iOS and Android releases. Misused background modes invite rejection. Persist user work frequently; the OS can kill you anytime after backgrounding. Uploads and timers should tolerate pause and resume without corrupting state.
Team process and long-term maintenance (5)
Shipping React Native features is less about any single API and more about the system around it: typed boundaries, predictable navigation, and telemetry that tells you what broke in production. Prefer boring, explicit modules over clever metaprogramming that the next hire cannot grep. When platform vendors change behavior in point releases, your defense is automated smoke tests on real devices and a short internal changelog of native assumptions you rely on.
Performance work should start with measurement, not instinct. Watch JS thread versus UI thread separately; they bottleneck differently. Lists, images, and animations dominate most regressions—optimize those before micro-optimizing pure functions. Hermes, JSC, and bridge internals evolve; re-profile after every major upgrade instead of trusting last year’s numbers. Battery and thermal throttling on mid devices reveal issues flagship phones hide.
Shipping and reliability habits (6)
Push notifications walk a line between helpful and intrusive. Prime users with context, respect notification channels on Android, and measure opt-outs after campaigns—spikes mean copy or frequency problems. Payload design affects background behavior; test killed and locked-device states. Tokens belong server-side with rotation strategies; never treat the client as authoritative for subscription state.
Images and maps look simple in mocks and expensive in production. Decode sizes should match display sizes; static map thumbnails are not interchangeable with interactive MapView gestures. Quotas, API keys, and offline behavior need explicit fallbacks—addresses as text, retry buttons, and calm error copy. Monitor vendor dashboards for spikes that indicate bugs or abuse.
Platform differences worth rehearsing (7)
Type-safe navigation pays off when routes multiply. Keep param lists near navigators, validate external URLs, and avoid serializing non-JSON-safe values through params. Renaming routes is a cross-cutting change—update analytics, push payloads, and E2E selectors in the same release train.
FlatList performance is configuration as much as code. Stable keys, reasonable `windowSize`, and memoized rows beat switching to a different list primitive blindly. Nested virtualized lists are a last resort—redesign first. Profile with production-like data volumes; dev placeholders lie.
Security, privacy, and data handling (8)
Reanimated and gesture libraries earn their place when profiling proves UI-thread work and your team can maintain native upgrades. Worklets have constraints—read errors carefully. Respect reduced motion and test Android timing differences—identical JS does not guarantee identical feel.
Analytics schema governance prevents warehouse disasters: version events, avoid high-cardinality strings, and align names across iOS, Android, and web. Consent gating must stop network calls, not just UI. Separate dev and prod projects to avoid polluting dashboards.
Performance and measurement discipline (9)
Project structure should make ownership obvious: routes as backbone, feature folders for product areas, thin screens, and shared infrastructure that is deliberately named. Refactor in vertical slices with device-tested releases—big-bang rewrites without tests are how teams lose weeks.
Helper modules concentrate glue code—storage, navigation, permissions—so screens stay readable. Split helpers by topic before files become merge-conflict magnets, and document each module’s contract. Good helpers answer ‘where do we save tokens?’ in one glance—not ‘ask Sarah.’
Team process and long-term maintenance (10)
Native modules are product decisions disguised as engineering tasks. You inherit Xcode and Gradle upgrades, store review scrutiny, and security obligations. Prefer maintained Expo modules and config plugins before writing JNI or Swift glue from scratch. When you must go native, budget pairing time with platform specialists and write runbooks for on-call—crashes in native code bypass many JS safeguards.
Deep links are a cross-team system: marketing URLs, hosted association files, entitlements, router params, and analytics query preservation. Debug with structured logging of raw URLs (scrub secrets) and reproduce cold-start races with auth hydration. Staging and production should be obviously separated—accidentally opening prod from a QA link erodes trust and pollutes data.
Shipping and reliability habits (11)
JWT and session refresh flows need single-flight refresh, clear logout semantics, and secure storage for refresh tokens when appropriate. Parallel 401s should not stampede refresh endpoints. Clock skew and biometrics policies belong in explicit product decisions, not accidental implementation details.
In-app purchases require server validation, restore flows, and support tooling that respects privacy. Sandbox quirks are normal—budget QA time. Subscriptions interact with family sharing, regional pricing, and refunds; engineering must stay aligned with finance and legal narratives users see in receipts.
Platform differences worth rehearsing (12)
Analytics schema governance prevents warehouse disasters: version events, avoid high-cardinality strings, and align names across iOS, Android, and web. Consent gating must stop network calls, not just UI. Separate dev and prod projects to avoid polluting dashboards.
Testing onboarding changes with funnel metrics beats debating opinions. Segment by acquisition channel and platform; back behavior differs. Skip paths must be genuine—dark patterns may win short metrics and destroy brand trust. Localization length tests prevent clipped CTAs in verbose languages.
Security, privacy, and data handling (13)
Environment variables should be classified: public-by-design, sensitive-with-mitigations, or never-on-device. `EXPO_PUBLIC_` values are extractable—treat them that way. Align env handling across EAS profiles and local dev; fail fast when keys are missing instead of shipping undefined behavior.
Project structure should make ownership obvious: routes as backbone, feature folders for product areas, thin screens, and shared infrastructure that is deliberately named. Refactor in vertical slices with device-tested releases—big-bang rewrites without tests are how teams lose weeks.
Performance and measurement discipline (14)
Accessibility is compatibility. Labels, focus order, and dynamic type are not polish—they determine whether users can complete tasks at all. Test with VoiceOver and TalkBack on hardware; simulators miss focus bugs. When designs prioritize minimalism, negotiate text alternatives for icon-only controls. Accessibility regressions often follow navigation redesigns—add checklist items to those PRs specifically.
Internationalization is a product feature, not a string swap. Plural rules, RTL layout, and locale-aware formatting change behavior—not just copy length. Pseudolocale helps find clipping early, but real Arabic and German QA catches nuance. Avoid concatenating translated fragments; context matters. Document glossary terms so translators do not invent inconsistent product names.
Team process and long-term maintenance (15)
Images and maps look simple in mocks and expensive in production. Decode sizes should match display sizes; static map thumbnails are not interchangeable with interactive MapView gestures. Quotas, API keys, and offline behavior need explicit fallbacks—addresses as text, retry buttons, and calm error copy. Monitor vendor dashboards for spikes that indicate bugs or abuse.
Error boundaries catch render failures, not native crashes or async mistakes. Pair them with platform crash reporting and structured client logs. Fallback UI should include build identifiers and humane copy—never raw stack traces for end users. Test fallbacks with screen readers; a broken error screen is still broken UX.