<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: George Fean</title>
    <description>The latest articles on DEV Community by George Fean (@gfean).</description>
    <link>https://dev.to/gfean</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4063188%2F02bb9f49-3c6b-412e-a71b-643fd8a39a9a.png</url>
      <title>DEV Community: George Fean</title>
      <link>https://dev.to/gfean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gfean"/>
    <language>en</language>
    <item>
      <title>Runtime Versions: The Compatibility Boundary for React Native OTA Updates</title>
      <dc:creator>George Fean</dc:creator>
      <pubDate>Wed, 19 Aug 2026 23:50:20 +0000</pubDate>
      <link>https://dev.to/gfean/runtime-versions-the-compatibility-boundary-for-react-native-ota-updates-3p6a</link>
      <guid>https://dev.to/gfean/runtime-versions-the-compatibility-boundary-for-react-native-ota-updates-3p6a</guid>
      <description>&lt;p&gt;A React Native application is not one artifact.&lt;/p&gt;

&lt;p&gt;The binary users install from the App Store or Play Store contains native code, native dependencies, platform configuration, a JavaScript engine, and an embedded JavaScript bundle.&lt;/p&gt;

&lt;p&gt;An OTA release replaces only part of that system: the JavaScript bundle and compatible bundled assets.&lt;/p&gt;

&lt;p&gt;That creates the most important safety question in any React Native OTA system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can this new JavaScript safely execute inside the native binary already installed on the device?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A runtime version exists to answer that question.&lt;/p&gt;

&lt;p&gt;In Bundle Drop, &lt;code&gt;runtimeVersion&lt;/code&gt; names the native compatibility boundary. An OTA bundle is only eligible when its platform and runtime version match the installed binary. It is not a “newer than” comparison and it is not simply another app version.&lt;/p&gt;

&lt;h2&gt;
  
  
  App version and runtime version solve different problems
&lt;/h2&gt;

&lt;p&gt;It is tempting to reuse the application version for everything.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;App version: 4.2.0
Runtime version: 4.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That may be convenient for a particular release strategy, but the two concepts are different.&lt;/p&gt;

&lt;p&gt;The application version answers something like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which product release is this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The runtime version answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which native interface can this JavaScript safely run against?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multiple JavaScript releases can therefore exist inside the same native runtime.&lt;/p&gt;

&lt;p&gt;And two native app releases can sometimes share a runtime if their native compatibility surface is unchanged.&lt;/p&gt;

&lt;p&gt;Bundle Drop records both kinds of identity separately; its CI result metadata, for example, includes the application version and &lt;code&gt;runtimeVersion&lt;/code&gt; as distinct fields.&lt;/p&gt;

&lt;p&gt;That separation matters because otherwise the OTA compatibility model becomes coupled to product-versioning decisions that may have nothing to do with native safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the runtime when the native boundary did not change
&lt;/h2&gt;

&lt;p&gt;Suppose your current configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runtimeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.2-native-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.2-native-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You fix some TypeScript logic.&lt;/p&gt;

&lt;p&gt;Or change copy.&lt;/p&gt;

&lt;p&gt;Or adjust styling.&lt;/p&gt;

&lt;p&gt;Or add a screen built entirely from native capabilities that already exist in the installed binary.&lt;/p&gt;

&lt;p&gt;Those changes do not necessarily create a new compatibility boundary.&lt;/p&gt;

&lt;p&gt;The existing binaries already contain what the new JavaScript needs.&lt;/p&gt;

&lt;p&gt;So the runtime can remain unchanged.&lt;/p&gt;

&lt;p&gt;Bundle Drop’s current public model explicitly recommends keeping a platform’s runtime literal stable for OTA-compatible JavaScript and asset changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bump the runtime when JavaScript can depend on new native state
&lt;/h2&gt;

&lt;p&gt;Now imagine adding a new iOS native module.&lt;/p&gt;

&lt;p&gt;The next JavaScript release imports that module.&lt;/p&gt;

&lt;p&gt;Older iOS binaries cannot safely run the new bundle because the native module simply does not exist inside them.&lt;/p&gt;

&lt;p&gt;Changing the OTA runtime label does not magically install the module.&lt;/p&gt;

&lt;p&gt;The correct sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;change the native code;&lt;/li&gt;
&lt;li&gt;create a new iOS runtime line;&lt;/li&gt;
&lt;li&gt;build and distribute a new iOS binary;&lt;/li&gt;
&lt;li&gt;publish OTA updates that depend on the new native capability only to that runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The old binary continues on its previous compatibility line.&lt;/p&gt;

&lt;p&gt;This same logic applies to changes such as native dependencies, permissions, generated native configuration, JavaScript-engine changes, architecture changes, or Expo config-plugin output that materially changes the native projects.&lt;/p&gt;

&lt;p&gt;A runtime version is a &lt;strong&gt;gate&lt;/strong&gt;, not an upgrade mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  iOS and Android do not have to move together
&lt;/h2&gt;

&lt;p&gt;This is one of the most useful properties of a per-platform runtime model.&lt;/p&gt;

&lt;p&gt;Suppose you add a native dependency only on iOS.&lt;/p&gt;

&lt;p&gt;You might change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;runtimeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;ios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.2-native-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.2-native-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;runtimeVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;ios&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.2-native-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;android&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;4.2-native-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android has not gained or lost any native capability.&lt;/p&gt;

&lt;p&gt;There is no compatibility reason to force it onto a new runtime line.&lt;/p&gt;

&lt;p&gt;Bundle Drop’s canonical runtime article explicitly models these as independent lanes: an iOS native change can advance iOS while Android continues resolving the previous Android runtime.&lt;/p&gt;

&lt;p&gt;This is more than a configuration detail.&lt;/p&gt;

&lt;p&gt;React Native teams frequently ship platform-specific native changes.&lt;/p&gt;

&lt;p&gt;Forcing both platforms to advance every time one platform changes creates unnecessary runtime fragmentation and release bookkeeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old and new binaries normally coexist
&lt;/h2&gt;

&lt;p&gt;Publishing a new store binary does not mean every user installs it immediately.&lt;/p&gt;

&lt;p&gt;Some users update quickly.&lt;/p&gt;

&lt;p&gt;Some wait days or weeks.&lt;/p&gt;

&lt;p&gt;Managed devices can lag even longer.&lt;/p&gt;

&lt;p&gt;During that period, production can legitimately contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Older iOS binary
runtimeVersion = ios:4.2-native-1

Newer iOS binary
runtimeVersion = ios:4.2-native-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both binaries may continue checking the same production channel.&lt;/p&gt;

&lt;p&gt;The important part is that each installation resolves only updates that belong to its own runtime line.&lt;/p&gt;

&lt;p&gt;Bundle Drop’s public resource explicitly describes channels containing several runtime lines while installed binaries receive only the exact compatible platform/runtime combination.&lt;/p&gt;

&lt;p&gt;This lets teams continue maintaining a safe OTA path for users who have not yet adopted the newest store release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime version and channel are different axes
&lt;/h2&gt;

&lt;p&gt;Another common mistake is treating channels as compatibility boundaries.&lt;/p&gt;

&lt;p&gt;They are not.&lt;/p&gt;

&lt;p&gt;A channel answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which release track should this app follow?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Examples might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;develop
beta
production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime answers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which native binaries can execute this update?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A production channel can therefore contain releases for multiple runtime lines.&lt;/p&gt;

&lt;p&gt;Likewise, two binaries on the same runtime could follow different channels.&lt;/p&gt;

&lt;p&gt;Bundle Drop documents these concepts separately because collapsing them creates awkward release models such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;production-ios-v4-native2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where release track, platform and compatibility are all encoded into one string.&lt;/p&gt;

&lt;p&gt;Keeping the axes independent produces a cleaner model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Channel: production
Platform: ios
Runtime: 4.2-native-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each value answers one question.&lt;/p&gt;

&lt;h2&gt;
  
  
  A runtime bump should follow a native change, not compensate for uncertainty
&lt;/h2&gt;

&lt;p&gt;It can be tempting to bump the runtime frequently “just to be safe.”&lt;/p&gt;

&lt;p&gt;That is safe in one narrow sense: fewer binaries will be considered compatible.&lt;/p&gt;

&lt;p&gt;But unnecessary runtime churn creates operational cost.&lt;/p&gt;

&lt;p&gt;Every new runtime line creates another compatibility branch that may coexist in production.&lt;/p&gt;

&lt;p&gt;Too coarse a boundary risks sending JavaScript to an incompatible binary.&lt;/p&gt;

&lt;p&gt;Too fine a boundary creates unnecessary fragmentation.&lt;/p&gt;

&lt;p&gt;The useful rule is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the runtime when the native compatibility surface changed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do not use it merely as a release counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expo introduces an authority question
&lt;/h2&gt;

&lt;p&gt;Expo projects have another concept called &lt;code&gt;runtimeVersion&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means an OTA integration needs to be explicit about which value is authoritative rather than silently mixing two independent runtime calculations.&lt;/p&gt;

&lt;p&gt;Bundle Drop’s default Expo setup uses the literal per-platform runtime values from &lt;code&gt;bundle.drop.config.js&lt;/code&gt;, with those values embedded into the native build and used by uploads.&lt;/p&gt;

&lt;p&gt;For advanced Expo workflows, the current public SDK also supports opting into Expo runtime authority. In that mode, Bundle Drop requires the runtime identity from the corresponding native build rather than guessing it during OTA publication; CI documentation describes using a matching local or EAS build receipt for that path.&lt;/p&gt;

&lt;p&gt;The broader principle is useful even outside Bundle Drop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one component should be authoritative for native compatibility.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the build system and OTA publisher derive that boundary differently, you have created exactly the ambiguity the runtime version was meant to remove.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime compatibility should be decided at release time
&lt;/h2&gt;

&lt;p&gt;You do not want to discover a compatibility mistake because a production device crashed.&lt;/p&gt;

&lt;p&gt;A useful release review asks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did native code change?&lt;/li&gt;
&lt;li&gt;Did a native dependency change?&lt;/li&gt;
&lt;li&gt;Did permissions or generated native configuration change?&lt;/li&gt;
&lt;li&gt;Did the JavaScript engine or architecture change?&lt;/li&gt;
&lt;li&gt;Did an Expo config-plugin change alter native output?&lt;/li&gt;
&lt;li&gt;Did either platform change independently?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is yes, review that platform’s runtime before publishing the JavaScript release.&lt;/p&gt;

&lt;p&gt;Bundle Drop’s setup and troubleshooting docs also surface runtime mismatches as a first-class delivery condition: an installation does not receive a bundle from a different runtime line.&lt;/p&gt;

&lt;p&gt;That fail-closed behavior is important.&lt;/p&gt;

&lt;p&gt;“No update available” is much safer than “run JavaScript against a native interface it was never built for.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Runtime version is not enough by itself
&lt;/h2&gt;

&lt;p&gt;Compatibility only answers whether a bundle &lt;em&gt;can&lt;/em&gt; run.&lt;/p&gt;

&lt;p&gt;It does not answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whether this is the correct release channel;&lt;/li&gt;
&lt;li&gt;whether the installation belongs in a targeting cohort;&lt;/li&gt;
&lt;li&gt;whether the staged-rollout percentage includes it;&lt;/li&gt;
&lt;li&gt;whether the bundle passed integrity verification;&lt;/li&gt;
&lt;li&gt;whether a previously installed update is healthy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those concerns need their own controls.&lt;/p&gt;

&lt;p&gt;This is why production OTA systems usually have several independent dimensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Platform
Runtime compatibility
Channel
Targeting
Rollout state
Release identity
Health / recovery state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runtime version is one of them, but it is arguably the first one.&lt;/p&gt;

&lt;p&gt;If the native compatibility decision is wrong, the rest of the release controls cannot make the JavaScript safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful mental model
&lt;/h2&gt;

&lt;p&gt;Think of a runtime version as a &lt;strong&gt;native contract identifier&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The installed binary says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I provide native contract &lt;code&gt;ios:4.2-native-1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An OTA bundle says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I require native contract &lt;code&gt;ios:4.2-native-1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the contracts match, the release can continue through the rest of the eligibility process.&lt;/p&gt;

&lt;p&gt;If they do not, the OTA bundle is not a candidate for that binary.&lt;/p&gt;

&lt;p&gt;That model remains understandable whether the app has one store version or five active versions, whether iOS and Android move together or separately, and whether the release arrives through a development, beta, or production channel.&lt;/p&gt;

&lt;p&gt;That is exactly what a good compatibility boundary should do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The release rule to keep
&lt;/h2&gt;

&lt;p&gt;OTA lets React Native teams move compatible JavaScript and assets independently from store binaries.&lt;/p&gt;

&lt;p&gt;That independence only remains safe when the OTA system respects the native application underneath it.&lt;/p&gt;

&lt;p&gt;Keep the runtime when the installed binaries already provide everything the update needs.&lt;/p&gt;

&lt;p&gt;Create a new runtime line when the native interface changes.&lt;/p&gt;

&lt;p&gt;Move only the platforms that actually changed.&lt;/p&gt;

&lt;p&gt;And allow old and new runtime lines to coexist while users adopt the new store binary at their own pace.&lt;/p&gt;

&lt;p&gt;Bundle Drop implements that model with literal per-platform runtime versions by default, plus an explicit Expo-authority option for teams that need it. The principle itself is provider-independent:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OTA compatibility should be explicit, stable and easy to explain during an incident.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bundledrop.app/resources/runtime-versions-compatibility-boundary" rel="noopener noreferrer"&gt;Originally published on Bundle Drop Resources&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Patch Delivery and Full-Bundle Reliability in React Native OTA</title>
      <dc:creator>George Fean</dc:creator>
      <pubDate>Wed, 12 Aug 2026 08:21:05 +0000</pubDate>
      <link>https://dev.to/gfean/patch-delivery-and-full-bundle-reliability-in-react-native-ota-1l4c</link>
      <guid>https://dev.to/gfean/patch-delivery-and-full-bundle-reliability-in-react-native-ota-1l4c</guid>
      <description>&lt;p&gt;Most OTA releases do not replace most of an application.&lt;/p&gt;

&lt;p&gt;A production update might fix one conditional, adjust a request handler, or change a small group of bundled assets.&lt;/p&gt;

&lt;p&gt;Yet with a full-bundle-only delivery model, every eligible device still downloads the complete JavaScript and asset payload.&lt;/p&gt;

&lt;p&gt;That model is easy to reason about. It is also often wasteful.&lt;/p&gt;

&lt;p&gt;Patch delivery solves that efficiency problem by transferring a representation of what changed instead of sending the entire release again.&lt;/p&gt;

&lt;p&gt;But patches introduce another question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when the device cannot safely use the patch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is where OTA delivery becomes more interesting than simply calculating the smallest possible download.&lt;/p&gt;

&lt;p&gt;A production system needs to optimize network transfer without making that optimization a requirement for reaching a valid release. That is the principle behind Bundle Drop’s hybrid approach: a patch can be the smaller route to a release, while the complete verified bundle remains available as the dependable route. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why patch delivery is useful
&lt;/h2&gt;

&lt;p&gt;Imagine a large React Native application where a release changes only a few lines of JavaScript.&lt;/p&gt;

&lt;p&gt;The generated production bundle still contains the rest of the application module graph, dependencies, framework code, and other unchanged content.&lt;/p&gt;

&lt;p&gt;If the update system always sends complete bundles, the amount of data transferred is tied more closely to the size of the application than to the size of the change.&lt;/p&gt;

&lt;p&gt;That matters because mobile networks are not uniform.&lt;/p&gt;

&lt;p&gt;Users can be on metered connections, high-latency networks, unstable Wi-Fi, or background execution windows that disappear before a large download completes.&lt;/p&gt;

&lt;p&gt;A smaller transfer generally spends less time exposed to those conditions and consumes less user bandwidth. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The advantage compounds when a team ships frequently.&lt;/p&gt;

&lt;p&gt;A sequence of small fixes does not need to mean repeatedly transferring the majority of the same JavaScript bundle to every device.&lt;/p&gt;

&lt;p&gt;When a client has the correct starting state, a patch can make the transfer cost look much more like the release itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The patch has a dependency that a full bundle does not
&lt;/h2&gt;

&lt;p&gt;A complete release bundle is self-contained from the client’s perspective.&lt;/p&gt;

&lt;p&gt;If the platform and runtime compatibility rules match, the device can validate the artifact without reconstructing it from a previously installed OTA state.&lt;/p&gt;

&lt;p&gt;A patch is different.&lt;/p&gt;

&lt;p&gt;It describes a transition from an expected source state to a target state.&lt;/p&gt;

&lt;p&gt;That means the source matters.&lt;/p&gt;

&lt;p&gt;This sounds simple until you think about a real production fleet.&lt;/p&gt;

&lt;p&gt;Some users skipped an earlier update.&lt;/p&gt;

&lt;p&gt;Some installed the application recently and only have the bundle embedded in the App Store or Play Store binary.&lt;/p&gt;

&lt;p&gt;Some were offline during a rollout.&lt;/p&gt;

&lt;p&gt;Some received an earlier release and then recovered from it.&lt;/p&gt;

&lt;p&gt;Others may have been in different rollout or targeting cohorts.&lt;/p&gt;

&lt;p&gt;Production devices rarely form one perfect linear chain of versions. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If a patch only works from version B to version C, a device currently running version A needs another route.&lt;/p&gt;

&lt;p&gt;That should not automatically become an application error.&lt;/p&gt;

&lt;p&gt;It can simply mean the optimized route is not appropriate for that device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat patching as an optimization, not a release prerequisite
&lt;/h2&gt;

&lt;p&gt;There are two obvious extremes.&lt;/p&gt;

&lt;p&gt;The first is to always send complete bundles.&lt;/p&gt;

&lt;p&gt;That gives every eligible device a self-contained artifact and keeps the delivery model simple.&lt;/p&gt;

&lt;p&gt;The downside is repeated over-transfer.&lt;/p&gt;

&lt;p&gt;The second extreme is to make patches mandatory.&lt;/p&gt;

&lt;p&gt;That minimizes transfer size when the expected source state exists, but every patch prerequisite becomes part of deployment correctness.&lt;/p&gt;

&lt;p&gt;Now you have to think about missing source versions, multiple patch paths, reconstruction failures, and devices whose local state does not match the expected chain.&lt;/p&gt;

&lt;p&gt;A better model is to separate the destination from the transport.&lt;/p&gt;

&lt;p&gt;The release is the complete verified result.&lt;/p&gt;

&lt;p&gt;The patch is one possible way of getting there.&lt;/p&gt;

&lt;p&gt;If the patch can safely produce that result, use it.&lt;/p&gt;

&lt;p&gt;If it cannot, fetch the complete release instead. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That gives the system an important invariant:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every successful delivery path converges on the same verified release.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Release selection and transport are separate problems
&lt;/h2&gt;

&lt;p&gt;Another useful distinction is between deciding &lt;strong&gt;which release&lt;/strong&gt; a device should receive and deciding &lt;strong&gt;how&lt;/strong&gt; that release should be transferred.&lt;/p&gt;

&lt;p&gt;A device might first be evaluated against:&lt;/p&gt;

&lt;p&gt;channel, platform, runtime compatibility, rollout percentage, targeting rules, and release state.&lt;/p&gt;

&lt;p&gt;Only after the system selects an eligible release does patch availability matter.&lt;/p&gt;

&lt;p&gt;Two devices can therefore be eligible for the same release but receive it differently.&lt;/p&gt;

&lt;p&gt;One may already have a suitable source bundle and use an optimized patch.&lt;/p&gt;

&lt;p&gt;Another may need the complete bundle.&lt;/p&gt;

&lt;p&gt;Both should converge on the same target release.&lt;/p&gt;

&lt;p&gt;Bundle Drop’s current upload documentation reflects that separation: uploads become versioned OTA bundles, patch optimization is prepared afterward, and eligible installations can use the full bundle while patch work is still being prepared. If patching is not efficient, the system deliberately uses the full bundle instead. (&lt;a href="https://bundledrop.app/docs/uploading" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification matters more than transfer size
&lt;/h2&gt;

&lt;p&gt;A patch being small does not make it safe.&lt;/p&gt;

&lt;p&gt;A successful HTTP response does not make it safe either.&lt;/p&gt;

&lt;p&gt;The useful boundary is whether the intended complete target exists locally and has passed the validation required before staging.&lt;/p&gt;

&lt;p&gt;A patch can download correctly and still fail to reconstruct the expected result.&lt;/p&gt;

&lt;p&gt;A transfer can be interrupted.&lt;/p&gt;

&lt;p&gt;Local data can be incomplete.&lt;/p&gt;

&lt;p&gt;The device can run out of storage.&lt;/p&gt;

&lt;p&gt;The application can be terminated midway through work.&lt;/p&gt;

&lt;p&gt;Those are ordinary failure modes in mobile software.&lt;/p&gt;

&lt;p&gt;The system should have a binary outcome:&lt;/p&gt;

&lt;p&gt;the complete intended release is present and verified, or the current working bundle remains active.&lt;/p&gt;

&lt;p&gt;There should not be a halfway state where partially reconstructed content becomes executable. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This is also why transport logic should ideally stay below the application-facing API.&lt;/p&gt;

&lt;p&gt;Product code should be able to ask for an update and receive a staged release without branching into separate business logic for “patch mode” and “full bundle mode.”&lt;/p&gt;

&lt;p&gt;The transport is an implementation decision.&lt;/p&gt;

&lt;p&gt;The release state is the application concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full-bundle fallback does not mean ignoring errors
&lt;/h2&gt;

&lt;p&gt;Fallback is not magic recovery.&lt;/p&gt;

&lt;p&gt;If the device has no usable connection, lacks storage, or cannot validate the complete artifact either, the update still cannot be staged.&lt;/p&gt;

&lt;p&gt;The correct behavior is to keep running the existing verified bundle and try again later according to the application’s update policy.&lt;/p&gt;

&lt;p&gt;The purpose of the full bundle is narrower:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a device should not be stranded just because the optimized patch route is unavailable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;Transport fallback handles a delivery-path problem.&lt;/p&gt;

&lt;p&gt;It does not fix a bad release.&lt;/p&gt;

&lt;p&gt;If the target JavaScript itself is faulty, that becomes a rollback or recovery problem instead. The canonical Bundle Drop resource explicitly treats transport reliability and release correctness as separate concerns. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Progressive rollouts make mixed device states normal
&lt;/h2&gt;

&lt;p&gt;Percentage rollouts intentionally create different device states.&lt;/p&gt;

&lt;p&gt;During a staged rollout, some installations have the candidate release while others still run the previous one.&lt;/p&gt;

&lt;p&gt;Targeted release rules can create additional valid branches.&lt;/p&gt;

&lt;p&gt;Server-side rollbacks can change future release selection.&lt;/p&gt;

&lt;p&gt;Device-side recovery can move an installation back to an earlier verified bundle.&lt;/p&gt;

&lt;p&gt;In other words, the fleet becomes heterogeneous by design.&lt;/p&gt;

&lt;p&gt;That makes a delivery model that assumes every device is always exactly one release behind increasingly fragile.&lt;/p&gt;

&lt;p&gt;Patch optimization can still work well in that environment, but only when it is allowed to be conditional.&lt;/p&gt;

&lt;p&gt;Devices that have the expected starting state use the smaller route.&lt;/p&gt;

&lt;p&gt;Devices with different histories use the complete bundle.&lt;/p&gt;

&lt;p&gt;The rollout system can then focus on release health instead of maintaining one perfectly uniform transport chain. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters operationally
&lt;/h2&gt;

&lt;p&gt;It is tempting to evaluate an OTA system by the smallest patch it can produce.&lt;/p&gt;

&lt;p&gt;That is not a very useful production metric on its own.&lt;/p&gt;

&lt;p&gt;A better set of questions is:&lt;/p&gt;

&lt;p&gt;Can every compatible and eligible installation reach the release?&lt;/p&gt;

&lt;p&gt;What happens when the optimized path cannot be used?&lt;/p&gt;

&lt;p&gt;Does the current working bundle remain active after a failed delivery?&lt;/p&gt;

&lt;p&gt;Do patch and full-bundle paths converge on the same staged result?&lt;/p&gt;

&lt;p&gt;Are old and unusual device histories tested before broad rollout?&lt;/p&gt;

&lt;p&gt;Those questions are less exciting than “our update was only 30 KB,” but they are much closer to the real job of release engineering. The source article makes the same point: predictable reachability and safe activation matter more than a perfect efficiency ratio. (&lt;a href="https://bundledrop.app/resources/patch-delivery-full-bundle-reliability" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Optimize both, force neither
&lt;/h2&gt;

&lt;p&gt;Patch delivery and full-bundle delivery are sometimes treated like opposing architectures.&lt;/p&gt;

&lt;p&gt;They do not have to be.&lt;/p&gt;

&lt;p&gt;A smaller transfer is useful because most releases change only part of an application.&lt;/p&gt;

&lt;p&gt;A complete release artifact is useful because production devices do not share one clean history.&lt;/p&gt;

&lt;p&gt;The two solve different problems.&lt;/p&gt;

&lt;p&gt;Patches optimize transport.&lt;/p&gt;

&lt;p&gt;Full bundles preserve an independent route to the release.&lt;/p&gt;

&lt;p&gt;Verification decides whether the result can be staged.&lt;/p&gt;

&lt;p&gt;That is the model Bundle Drop currently uses for React Native and Expo OTA delivery: the public SDK describes hybrid transport as maintaining full-bundle integrity while supported devices download patch-sized changes. (&lt;a href="https://github.com/GFean/react-native-bundle-drop" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The important idea is broader than any one platform:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bandwidth optimization should not become a new condition for deployment correctness.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The original technical resource for this article is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://bundledrop.app/resources/patch-delivery-full-bundle-reliability&lt;/code&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>javascript</category>
      <category>mobile</category>
      <category>devops</category>
    </item>
    <item>
      <title>Life After CodePush: Choosing an OTA Update Strategy for React Native in 2026</title>
      <dc:creator>George Fean</dc:creator>
      <pubDate>Tue, 04 Aug 2026 22:55:03 +0000</pubDate>
      <link>https://dev.to/gfean/life-after-codepush-choosing-an-ota-update-strategy-for-react-native-in-2026-56mi</link>
      <guid>https://dev.to/gfean/life-after-codepush-choosing-an-ota-update-strategy-for-react-native-in-2026-56mi</guid>
      <description>&lt;p&gt;Microsoft retired the hosted Visual Studio App Center service on March 31, 2025. That included the managed service many React Native teams knew through CodePush, although Microsoft also released a self-hostable CodePush server.&lt;/p&gt;

&lt;p&gt;The service retirement did not remove the underlying need. Teams still want to fix JavaScript bugs, adjust UI, update copy, and ship compatible asset changes without waiting for a new App Store or Play Store release.&lt;/p&gt;

&lt;p&gt;But choosing a replacement is not just a matter of finding a familiar &lt;code&gt;sync()&lt;/code&gt; call. An OTA system becomes part of your production runtime and your release process. The important questions are about compatibility, failure handling, rollout control, and how much of the system your team wants to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an OTA system actually has to manage
&lt;/h2&gt;

&lt;p&gt;An OTA update is not simply a file download. It is a decision about which code a particular installed binary can safely execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native and runtime compatibility
&lt;/h3&gt;

&lt;p&gt;React Native lets you change JavaScript without rebuilding the native application, but only while that JavaScript remains compatible with the native code already on the device.&lt;/p&gt;

&lt;p&gt;Adding a native module, changing permissions, upgrading React Native, switching important native configuration, or changing a JavaScript engine can require a new binary. A production OTA system therefore needs a compatibility boundary—often called a runtime version—and must refuse to deliver an update across that boundary.&lt;/p&gt;

&lt;p&gt;This is one of the easiest details to overlook in a proof of concept and one of the most important details in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Channels and release tracks
&lt;/h3&gt;

&lt;p&gt;Most teams need more than one stream of updates. You may want production, beta, QA, internal, or customer-specific tracks.&lt;/p&gt;

&lt;p&gt;A channel is useful only if its relationship to builds and updates is explicit. Teams should know which installed binaries read from which channel, how an update moves between channels, and whether promoting a tested release preserves its identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Safe rollout strategies
&lt;/h3&gt;

&lt;p&gt;Sending an update to every eligible device at once is fast, but it gives you very little time to react.&lt;/p&gt;

&lt;p&gt;Percentage-based rollouts let you expose a release gradually. Targeted rollouts go further by limiting eligibility using properties such as plan, region, or an internal beta flag. These controls solve different problems: targeting defines who is eligible, while staging controls how much of that group receives the update.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rollback and recovery
&lt;/h3&gt;

&lt;p&gt;A dashboard rollback is helpful, but device-side recovery matters too. Consider what happens when an update crashes before the app can check the server again.&lt;/p&gt;

&lt;p&gt;A robust design should define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when an update is considered healthy;&lt;/li&gt;
&lt;li&gt;how repeated launch failures are detected;&lt;/li&gt;
&lt;li&gt;whether the device can return to a last-known-good bundle;&lt;/li&gt;
&lt;li&gt;whether the embedded bundle remains a final fallback; and&lt;/li&gt;
&lt;li&gt;how a failed update is prevented from being retried on the same device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worst time to discover that rollback depends on a successful JavaScript launch is after shipping JavaScript that cannot launch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrity verification
&lt;/h3&gt;

&lt;p&gt;The client should verify that the downloaded artifact is exactly the artifact the service intended to deliver. That normally means secure transport plus cryptographic hashes or signatures, validation before activation, and an atomic transition so a partial install never becomes the active version.&lt;/p&gt;

&lt;p&gt;Integrity and compatibility are separate checks. A perfectly downloaded bundle can still be wrong for a device if it targets a different native runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download size and bandwidth
&lt;/h3&gt;

&lt;p&gt;Some systems deliver a complete JavaScript bundle and asset set for every update. Others support patches or asset-level deltas.&lt;/p&gt;

&lt;p&gt;Patch delivery can reduce download size when a change is small, but it introduces another failure path. Ask whether the client reconstructs and verifies the complete target state and whether it can fall back to a full update when a patch is missing, unsupported, or invalid.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational visibility
&lt;/h3&gt;

&lt;p&gt;At minimum, teams need to identify which release is running when an error occurs. Useful signals include bundle identifiers or hashes, adoption, download and install failures, rollout state, and source maps tied to the exact update.&lt;/p&gt;

&lt;p&gt;OTA tooling should complement your crash reporter, not hide update identity from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main approaches available
&lt;/h2&gt;

&lt;p&gt;There is no universal winner. The right choice depends on your application model, operational appetite, existing tooling, and risk tolerance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expo Updates and EAS Update
&lt;/h3&gt;

&lt;p&gt;The names are easy to blur together, so it helps to separate them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;expo-updates&lt;/code&gt; is the client library that loads compatible updates.&lt;/li&gt;
&lt;li&gt;EAS Update is Expo's hosted cloud service for publishing and serving those updates.&lt;/li&gt;
&lt;li&gt;EAS Build is a separate service that produces app binaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://docs.expo.dev/eas-update/introduction/" rel="noopener noreferrer"&gt;EAS Update&lt;/a&gt; supports Expo projects using Continuous Native Generation and existing React Native projects that install &lt;code&gt;expo-updates&lt;/code&gt;. It uses runtime versions for native compatibility, supports staged rollouts, provides deployment insights, and lets teams republish a previous stable update.&lt;/p&gt;

&lt;p&gt;For teams already standardized on Expo tooling, that integration can be a strong advantage. Evaluate the workflow conventions, update behavior, and pricing model against your needs rather than treating "uses React Native" as the only requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other managed OTA providers
&lt;/h3&gt;

&lt;p&gt;A managed provider can reduce the amount of infrastructure your team owns. The tradeoff is that you are adopting the provider's runtime model, release controls, pricing dimensions, and migration path.&lt;/p&gt;

&lt;p&gt;Compare providers on concrete behavior rather than broad feature labels. "Rollback," for example, might mean republishing an older release, switching a server-side pointer, or performing automatic recovery on the device. Those are not operationally identical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-hosted or custom infrastructure
&lt;/h3&gt;

&lt;p&gt;Microsoft's &lt;a href="https://learn.microsoft.com/en-us/appcenter/retirement" rel="noopener noreferrer"&gt;App Center retirement guidance&lt;/a&gt; points CodePush users to a self-hostable server. Expo also documents self-hosting for the updates protocol. A custom system can offer maximum control over data, cost, and release policy.&lt;/p&gt;

&lt;p&gt;That control comes with ownership. Your team becomes responsible for artifact storage, signing, availability, client compatibility, rollout state, observability, abuse prevention, and incident response. Running an update server is not the same as safely operating an OTA lifecycle.&lt;/p&gt;

&lt;p&gt;Self-hosting is a reasonable choice when the constraints justify it and the team is prepared to maintain it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store-only releases
&lt;/h3&gt;

&lt;p&gt;Choosing not to use OTA is also valid.&lt;/p&gt;

&lt;p&gt;Store-only releases keep the delivery model simple and ensure every code change passes through the normal binary pipeline. The cost is slower delivery for small fixes and less control over when users install them.&lt;/p&gt;

&lt;p&gt;For apps with infrequent releases, strict review requirements, or a small operational team, that tradeoff may be appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions to ask before choosing
&lt;/h2&gt;

&lt;p&gt;A useful evaluation should be specific enough that two engineers can reach the same answer from the documentation or a test.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does it support our actual project shape: Expo, Expo Prebuild/CNG, EAS Build, or bare React Native?&lt;/li&gt;
&lt;li&gt;How is native compatibility represented, and who is responsible for changing the runtime version?&lt;/li&gt;
&lt;li&gt;Does every update download a full bundle, or can the client fetch a smaller patch when only part of the application changes?&lt;/li&gt;
&lt;li&gt;If patching fails, is there a verified full-bundle fallback?&lt;/li&gt;
&lt;li&gt;Are percentage rollouts available? Can targeting rules use application-defined user properties?&lt;/li&gt;
&lt;li&gt;What happens if an update repeatedly crashes before startup completes?&lt;/li&gt;
&lt;li&gt;Can the device recover without successfully running the new JavaScript?&lt;/li&gt;
&lt;li&gt;Are channels, promotions, and rollback operations auditable?&lt;/li&gt;
&lt;li&gt;Can source maps and release identifiers be connected to the existing error tracker?&lt;/li&gt;
&lt;li&gt;Is pricing tied to monthly active users, bandwidth, storage, builds, seats, or some combination?&lt;/li&gt;
&lt;li&gt;Can artifacts and release history be exported if the team later migrates away?&lt;/li&gt;
&lt;li&gt;Which changes still require a store build?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A small failure drill is often more revealing than another feature checklist: publish a deliberately broken update to a test channel, then observe how rollout, detection, rollback, and device recovery actually behave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built Bundle Drop
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I am the creator of Bundle Drop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/GFean/react-native-bundle-drop" rel="noopener noreferrer"&gt;Bundle Drop&lt;/a&gt; because I wanted another managed option with an explicit compatibility boundary and a recovery model that remains useful when an update fails early.&lt;/p&gt;

&lt;p&gt;Its delivery model prepares a complete verified bundle and, when possible, a smaller patch. A device can download the patch when only part of the application changed instead of downloading the full update every time. The client reconstructs and verifies the complete target state before activation. If patch transport is unavailable or fails, it falls back to the signed full bundle.&lt;/p&gt;

&lt;p&gt;Bundle Drop also provides channels, staged rollouts, and targeting based on application-defined user properties. On-device health checks can return a failing update to the previous verified bundle, or to the binary's embedded bundle when there is no earlier OTA version.&lt;/p&gt;

&lt;p&gt;For Expo, the integration is designed for Prebuild/CNG projects and normal EAS Build workflows. EAS Build still creates the application binary; Bundle Drop handles the OTA layer after that binary is deployed. The same runtime-version model also supports bare React Native.&lt;/p&gt;

&lt;p&gt;The service's plans are not priced by monthly active users. That was an intentional choice because I wanted teams to evaluate rollout and storage needs without treating user growth itself as an OTA cost.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://bundledrop.app/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; describes the runtime and release model, and the &lt;a href="https://bundledrop.app/docs/expo" rel="noopener noreferrer"&gt;Expo integration guide&lt;/a&gt; covers config-plugin setup, CNG, EAS Build, and runtime authority. Bundle Drop is one option among several, and I would still recommend testing its failure behavior against your own release requirements before choosing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose for recovery, not just installation
&lt;/h2&gt;

&lt;p&gt;Installation is the beginning of an OTA system, not the hard part.&lt;/p&gt;

&lt;p&gt;Before committing to a provider or a self-hosted design, write down your native compatibility rule, rollout process, health signal, rollback path, observability requirements, and exit strategy. Then test those rules with real release builds.&lt;/p&gt;

&lt;p&gt;The best choice is the one your team can understand and operate during an incident—not merely the one with the shortest setup guide.&lt;/p&gt;

&lt;p&gt;If you are evaluating the post-CodePush landscape, I would be interested in hearing which operational requirements have mattered most to your team.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>mobile</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
