Publishing Hot Updates
Now that your app can detect updates, let's learn how to publish and update it. See the flow below:
- We first need to build a native release version. Before building, ensure
react-native-updateis integrated, tested, and works correctly. For Android, disablecrunchPngs. See documentation for iOS Build and Android Build. After building, runcresc uploadIpa,cresc uploadApk, orcresc uploadAabto upload the package to Cresc servers to serve as the baseline for delta comparisons. Keep a copy of this installation package; the package distributed to usersmust be strictly identicalto the uploaded one. We recommend using git tags for native versioning (e.g.,v1.0.0). - Iterate on your business logic over the baseline (add/remove JS code, static assets). Run
cresc bundleto generate and publish a hot update without recompiling the native app. We recommend using git tags for hot update versioning (e.g.,v1.0.1). - If there are native changes during iteration, you must publish and upload a new native baseline version (repeat step 1, but set a different native version number). You can maintain just one native baseline or multiple versions concurrently.
Publishing Native Baseline Version
iOS
Refer to Running On Device to ensure you are using the offline bundle.
Follow the standard flow to archive the .ipa file:
- In Xcode, select a real device or Generic iOS Device.
- Go to Product - Archive.
- After Archiving, select
Exportto generate the .ipa file. - Run the following command to upload it:
The CFBundleShortVersionString in ios/[project]/Info.plist will be recorded as the packageVersion.
You can now upload this version to the App Store, or test it on devices via TestFlight. Note: Testing hot updates directly via Xcode is not supported yet.
If you re-archive later (e.g., modifying native code/configs), you must change the version number, and uploadIpa again. Otherwise, identically versioned native packages can produce mismatched build timestamps, which do not block updates but can reduce diff reuse and increase download size.
Android
Set up signing per Android Signed APK. Run ./gradlew assembleRelease or ./gradlew aR in the android folder. The APK will be under android/app/build/outputs/apk/release/app-release.apk.
If you need to distribute .aab to Google Play and .apk to other channels, add an npm script to the root package.json that runs assembleRelease and bundleRelease in the same Gradle invocation. This lets the APK and AAB reuse the same release build outputs, keeping the embedded bundle and build timestamp aligned. You can then distribute the format required by each channel. If your project already has a scripts field, add only this script:
The outputs are:
If your project uses flavors, adjust the task names in the npm script for the actual variant, for example assembleProdRelease and bundleProdRelease. Avoid running assembleRelease in one Gradle command and bundleRelease in another, because the two packages may end up with different build timestamps.
Upload the format you actually distribute:
The versionName in android/app/build.gradle is recorded as the packageVersion.
You can now publish this version to app markets or install the APK directly for testing. If the same version produced both APK and AAB, distribute the format required by each channel: Google Play usually uses AAB, while direct install and third-party markets commonly use APK.
If you rebuild native code later, you must change the version number, and upload the corresponding native package again. Otherwise, build timestamp mismatches do not block updates but can reduce diff reuse and increase download size.
Publishing Hot Update Version
Modify a line of code, and run cresc bundle --platform <ios|android> to generate a new hot update version.
If you use frameworks without index.js like modern expo, the bundle command will fail. Manually create an index.js file importing the framework's entry file, referring to main in package.json. For expo, index.js looks like:
Input Y to upload immediately, or run cresc publish --platform android build/output/android.1459850548545.ppk later.
The version is stored on the server, but users cannot see it until you bind native packages to it.
Input Y to bind immediately, or run cresc update --platform <ios|android> later to bind previously uploaded versions. You can also drag and drop native versions to matching hot updates on the web dashboard.
After binding, the server takes a few seconds to generate diff patches, and clients will receive updates.
To publish new updates thereafter, repeatedly run cresc bundle without recompiling native code.
Congratulations! You have completed the hot update integration.
Canary Release (Gradual Rollout)
Canary releases mitigate risk by gradually expanding the update scope to test stability.
What is a Canary Release?
Before pushing updates globally, you push them to a small subset (e.g., 5%, 10%) of users, observe their metrics, and gradually widen the scope to 100%.
Benefits
- Lower Risk: Bugs only affect small subsets, enabling swift rollbacks.
- Verify Stability: Observe performance across varied real-world networking environments.
- Smooth Transitions: Prevents severe server CPU usage spikes during mass updates.
- Fast Recovery: Halts rollouts immediately minimizing global impact.
How it Works
When you configure a canary percentage (e.g., 10%), update queries calculate a hash using the device's UUID:
- Users within the bucket receive the newest updates.
- Users outside receive the previous full version or no update.
- The hash remains stable; multiple checks won't flip a user's bucket state.
Usage
Web Dashboard
- Log into the Cresc Dashboard.
- Select App and Native Version.
- Click "Publish".
- Adjust the rollout percentage.
Command Line
Review the rollout parameter in the CLI docs.
Notes
Important: Canary versions form independent bindings with Native packages.
- One Canary Version At A Time: Each Native Version can bind to one Canary update (<100%) and one Full update concurrently.
- Priority: Users inside the canary bucket receive the canary update. Others receive the Full update.
- Promoting to Full: Bumping the percentage to 100% promotes the canary to a full release automatically, replacing any older full releases.
- Client Requirements: Features require
react-native-update>= 10.32.0.
Force Boot (Brick Rescue)
Force boot is a flag on the binding between a hot update version and a native package: the client's native cold-start check activates that version right after downloading it (effective on the next launch), overriding the in-app update strategy (updateStrategy / checkStrategy).
Why it exists
If a hot update kills your app's JS (crash on launch, white screen), the JS code on that device never gets another chance to run the update check — the regular OTA channel is dead. That device is "bricked".
Since react-native-update 10.52.1, the client runs an independent background check from native code a few seconds after every cold start, with no JS involved. Force boot is the directive for that channel: even with JS dead, the native check can still pull down the fix and activate it, so the device recovers on its next launch.
Usage
- Log in to the Cresc console
- Find the fixed version and publish it with "Full + force boot (rescue)", or for an already-bound version, choose "Force boot (rescue)" from the package menu
- Review the semantics in the confirmation dialog and publish
Notes
Force boot overrides the user-side update strategy. Reserve it for emergencies like brick rescue, and make sure the fix version is verified before publishing.
- Client requirement: the native package must ship
react-native-update>= 10.52.1 (older clients lack the full native cold-start check capability, and the console hides the option for them) - Native check only: the interactive JS update flow ignores this flag
- Safety guards still apply: the device-local rollback guard takes precedence over force boot, and first-launch crash protection still rolls back automatically if the activated version crashes
- The flag lives on the binding: republishing (rebinding) replaces the binding and clears the flag; you can also choose "Cancel force boot" from the package menu