integrationReact NativeExpoOTA updatesSDK

How to Integrate NitroPush with React Native and Expo

Install the NitroPush SDK, configure Expo or bare React Native, confirm healthy launches, sync updates, sign bundles, and upload your first OTA release.

N
NitroPush Engineering··11 min read

NitroPush delivers compatible JavaScript bundles and assets to installed React Native and Expo applications. This guide covers the hosted NitroPush path: native configuration supplies the deployment key, JavaScript calls the no-argument configure() function, and the CLI publishes releases.

For the system-level explanation first, read how NitroPush works.

Prerequisites

  • A React Native or Expo application with iOS and/or Android release builds
  • Node.js and the native toolchains required by your project
  • A NitroPush account and organization
  • A project ID and deployment key for a test, stage, or production environment

1. Install the SDK

npm install @nitropush/react-native react-native-nitro-modules
# or
yarn add @nitropush/react-native react-native-nitro-modules

The same JavaScript package supports Expo and bare React Native. Native wiring differs by project type.

2. Create a project and environment

Install the CLI and sign in:

npm install -g nitropush
nitropush login
nitropush whoami --json

Create an application. --org is optional when login already saved a default organization, but using it explicitly makes setup scripts easier to audit.

nitropush app create --org <org-id> --name "My App"
nitropush env create --app <project-id> --name prod

Save the real deployment key printed by env create. Never invent one or expose it through a public JavaScript environment variable.

3A. Expo: use the config plugin

Use a dynamic Expo app config so the plugin can read the deployment credential from a private build environment variable without committing it or exposing it to JavaScript:

// app.config.js
module.exports = {
  "expo": {
    "plugins": [
      [
        "@nitropush/react-native",
        {
          "deploymentKeyEnvVar": "NITROPUSH_DEPLOYMENT_KEY",
          "ios": true,
          "android": true
        }
      ]
    ]
  }
};

deploymentKeyEnvVar defaults to NITROPUSH_DEPLOYMENT_KEY. Set that private variable in the environment that runs local prebuilds and CI/EAS builds. Do not prefix it with EXPO_PUBLIC_, because Expo embeds public variables in the JavaScript bundle.

Then regenerate and rebuild the native projects:

npx expo prebuild

The plugin writes the deployment key into native configuration and injects the native bundle-resolution hooks. A native rebuild is required; reloading Metro is not enough. The same is true when enabling or disabling Delta Updates: change enableDeltaUpdates in the plugin, run prebuild, and rebuild the native app.

3B. Bare React Native: wire the native hosts

Bare applications set NITROPUSH_DEPLOYMENT_KEY in Info.plist and the Android <application> metadata, install the SDK early in application startup, and serve the active NitroPush bundle only in release builds.

The exact AppDelegate.swift and MainApplication.kt structure varies across React Native versions. Use the maintained snippets in the bare React Native installation guide instead of copying an old AppDelegate template.

Two safeguards are mandatory:

  • Debug builds must continue to use Metro.
  • Release builds must fall back to the binary-shipped bundle when no valid OTA bundle exists.

After iOS changes, install pods and rebuild the native applications.

4. Configure at module scope

For hosted NitroPush, configure() takes no arguments. It reads the deployment key from native configuration and initializes the native singleton before React renders.

import {
  configure,
  sync,
  InstallMode,
  SyncStatus,
} from '@nitropush/react-native';

const client = configure();

Do not move configure() into a component or useEffect. Use configureWith() only for an explicitly self-hosted server or custom CDN configuration.

5. Confirm the first healthy render

notifyAppReady() tells the rollback safety system that the active bundle reached a known-good state.

import { useEffect } from 'react';

export default function App() {
  useEffect(() => {
    client.notifyAppReady().catch((error) => {
      console.error('NitroPush app-ready confirmation failed', error);
    });
  }, []);

  return <YourApplication />;
}

Skipping this call can make a healthy update appear unconfirmed and trigger recovery behavior on the next launch.

6. Check for updates

Call sync() at the lifecycle point that matches your product experience—for example app foreground, after login, or from a manual settings button.

await sync(
  client,
  { installMode: InstallMode.ON_NEXT_RESUME },
  (status, error) => {
    if (status === SyncStatus.UNKNOWN_ERROR) {
      console.error('NitroPush sync failed', error);
    }
  },
  ({ receivedBytes, totalBytes }) => {
    console.log(`${receivedBytes} / ${totalBytes}`);
  },
);

Concurrent sync requests for the same client share the in-flight operation instead of racing.

7. Enable bundle signing for production

Generate an ECDSA P-256 keypair and register the public key:

nitropush app signing-key generate \
  --app <project-id> \
  --out ./nitropush-signing.pem

PEM files are ignored by this repository’s secret rules, but you should still store the production private key in a secret manager. Never pass PEM contents directly on a command line. Write the secret to a temporary file in CI and pass the path with --signing-key.

8. Upload the first release

Build or export your bundle, then publish it with explicit targeting:

nitropush release upload \
  --project <project-id> \
  --environment prod \
  --runtime-version 1.0.0 \
  --label 1.0.1 \
  --bundle-path ./dist \
  --signing-key ./nitropush-signing.pem

The CLI detects Expo export metadata or a CodePush-style Hermes/JavaScript bundle. If signing is not configured for the project, omit --signing-key. Add --delta only after validating full-bundle delivery and the delta path in a non-production environment.

9. Verify the failure path

Before production, test more than a successful download:

  1. Confirm debug builds still load Metro.
  2. Confirm a release build starts from the embedded bundle.
  3. Publish a staging update and verify download, activation, and notifyAppReady().
  4. Verify an invalid signature is rejected in an isolated test environment.
  5. Verify an unconfirmed bundle returns to the previous known-good bundle.
  6. Start with a limited rollout and expand only after observing real-device outcomes.

Next steps