OTA updates for Rock apps: faster native builds, and no rebuild for JavaScript fixes
Rock makes native builds fast. BetterCodePush makes them unnecessary for JavaScript changes. How to add over-the-air updates to a Rock project, and how the two fit into one release process.
Rock is Callstack's toolkit for building React Native apps. Teams adopt it to move off the Community CLI, to add React Native to existing native apps, and above all for its remote build cache, which reuses native build artifacts across machines and CI instead of compiling them again.
A fast native build still has to go through app review before it reaches users. For a JavaScript bug fix, that is the slow part. Over-the-air updates remove it: the fix goes straight to the apps already installed, the next time people open them. BetterCodePush 1.0 supports Rock projects out of the box.
Why Rock needs its own build adapter
An OTA update replaces the JavaScript bundle inside an app that is already installed, so the update has to be built the way your project builds bundles. In a bare React Native project that is React Native's own bundle command. In a Rock project it is rock bundle.
The @bettercodepush/rock adapter builds every update with rock bundle in your project, compiled to Hermes bytecode by default, so your updates come from the same toolchain as the rest of your app.
Setting it up
You need a BetterCodePush account with an app for each platform you ship, and an API key, both created in the console. Then, in your Rock project:
npx bettercodepush init --build rock
npx pod-install # if you ship iOSinit installs the SDK, the Rock adapter and the hosted service plugin, embeds your app IDs in the native projects, and wires BetterCodePush into AppDelegate and MainApplication. It recognises a Rock project from its rock.config file, so --build rock only makes the choice explicit.
The generated bettercodepush.config.ts looks like this:
import { defineConfig } from "@bettercodepush/cli";
import { rock } from "@bettercodepush/rock";
import { bettercodepushSaaS } from "@bettercodepush/plugin-saas";
import { config } from "dotenv";
config({ path: ".env.bettercodepush" });
export default defineConfig({
build: rock({ entryFile: "index.js", hermes: true }),
...bettercodepushSaaS({
appIds: { ios: "app_...", android: "app_..." },
apiKey: process.env.BETTERCODEPUSH_API_KEY!,
}),
updateStrategy: "appVersion",
});The API key stays in .env.bettercodepush, which is ignored by Git, or in your CI secrets. It is only used by the CLI to publish releases and is never embedded in the app.
Finally, wrap your root component so the app checks for updates:
import { BetterCodePush } from "@bettercodepush/react-native";
export default BetterCodePush.init({}).wrap({ updateStrategy: "appVersion" })(App);Build and release the app to the stores as you normally do with Rock. That build is the one that can receive updates.
Shipping an update
npx bettercodepush deploy --platform ios --channel production --rollout 10deploy runs rock bundle, uploads the result and creates a release for that app version. Devices download it in the background and run it on their next launch. Because devices only download what changed since the release they are running, a small fix is a small download.
Starting at 10% and widening once it looks healthy is the safest habit. Our guide to staged rollouts and rollbacks covers the full routine, and Insights shows how a release is doing on real devices while you wait.
Rock and OTA updates in one release process
The two tools cover the two halves of a React Native app:
- Native change (a new native module, an SDK upgrade, a React Native upgrade): build with Rock, where the remote cache skips whatever has not changed, bump the app version and release through the stores.
- JavaScript change (a bug fix, copy, a screen that needs work): ship it with
bettercodepush deployto every installed copy of the matching app version, without a native build at all.
Updates are matched to the app version they were built for. As long as every native change comes with a new app version, a bundle that expects new native code never reaches a build that does not have it. Not sure which side of the line a change falls on? Our guide to what you can ship over the air walks through it.
Readable crash reports
Hermes stack traces from an OTA bundle are only useful with that bundle's source map. Set sourcemap: true in the Rock adapter and wrap it with the BetterCodePush plugin for Sentry, Bugsnag or Datadog. Each deploy then uploads the map to your crash reporter, so errors from an OTA release point at your source files.
Get started
Rock support is included in every plan, Hobby included. Create an account at console.bettercodepush.com, run npx bettercodepush init --build rock and ship your first update. Moving from another OTA service? See our comparison of CodePush alternatives and the migration guides on the blog.