Metro (React Native)

Deploy your React Native applications with Module Federation to Zephyr Cloud using Metro bundler. The Zephyr Metro plugin integrates seamlessly with Metro's build process and enables micro-frontend architectures for React Native applications.

Prerequisites
Quick Setup with Codemod
npm
yarn
pnpm
bun
npx with-zephyr

This detects your bundler and configures Zephyr automatically. Learn more →

For manual setup, continue below.

Installation

Install the Metro plugin and required Module Federation dependencies in your project:

npm
yarn
pnpm
bun
deno
npm add --dev zephyr-metro-plugin @module-federation/metro@^2.9.0 @module-federation/runtime

To enable React Native OTA updates in a host app, also install the native cache runtime:

npm
yarn
pnpm
bun
deno
npm add zephyr-native-cache

Configuration and Publication

Metro configuration and publication are separate stages:

StagePurposeCreates dashboard records
withZephyr()Resolves remotes and generates the local Zephyr manifest when Metro loads its configurationNo
bundle-mf-remote / bundle-mf-hostBuilds and uploads artifacts to Zephyr CloudYes

Loading withZephyr() can request /application-config and /build-id, but those requests do not persist an organization membership, project, application, or application version. Publication reaches /build-stats; the first successful upload creates the dashboard entities for that application identity. A token claim of can_write: true grants permission to create that identity. It does not mean the records already exist.

You must register the publication commands through either the React Native CLI configuration or the RNEF plugin below, then run the appropriate bundle-mf-* command. start, run-ios, and run-android load Metro configuration but do not publish artifacts.

Application Identity

By default, Zephyr infers the organization and project from the Git origin remote and the application name from the nearest package.json. To override that identity, add a zephyr.config.ts file:

zephyr.config.ts
export default {
  org: 'acme',
  project: 'mobile',
  appName: 'mini-app',
};

The resulting application UID is mini-app.mobile.acme. See Declarative Configuration for file discovery, precedence, and other options.

Module Federation with Metro

Metro bundler supports Module Federation through the @module-federation/metro package, allowing you to create host and mini applications (remotes) in React Native.

Mini Application (Remote)

Mini applications expose modules to be consumed by host applications:

// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withModuleFederation } = require('@module-federation/metro');
const { withZephyr } = require('zephyr-metro-plugin');

const config = {
  resolver: { useWatchman: false },
};

const mfConfig = {
  name: 'miniApp',
  filename: 'miniApp.bundle',
  exposes: {
    './example': './src/example.tsx',
  },
  shared: {
    react: {
      singleton: true,
      eager: false,
      requiredVersion: '19.1.0',
      version: '19.1.0',
      import: false,
    },
    'react-native': {
      singleton: true,
      eager: false,
      requiredVersion: '0.80.0',
      version: '0.80.0',
      import: false,
    },
  },
  shareStrategy: 'version-first',
};

async function getConfig() {
  const baseConfig = mergeConfig(getDefaultConfig(__dirname), config);
  const zephyrConfig = await withZephyr({
    name: mfConfig.name,
    target: process.env.ZEPHYR_TARGET === 'android' ? 'android' : 'ios',
  })(baseConfig);

  return withModuleFederation(zephyrConfig, mfConfig, {
    flags: {
      unstable_patchHMRClient: true,
      unstable_patchInitializeCore: true,
      unstable_patchRuntimeRequire: true,
    },
  });
}

module.exports = getConfig();

React Native CLI Publication Commands

Create or modify react-native.config.js in each host and remote application. The adapter registers both publication commands while preserving existing React Native CLI settings:

// react-native.config.js
const { zephyrMetroReactNativeCli } = require('zephyr-metro-plugin');

const config = {
  assets: ['./assets'],
};

module.exports = {
  ...config,
  commands: [
    ...(config.commands ?? []),
    ...zephyrMetroReactNativeCli().commands,
  ],
};

RNEF Publication Commands

RNEF projects must register the Zephyr RNEF plugin. It provides the same bundle-mf-host and bundle-mf-remote publication commands:

rnef.config.mjs
import { platformAndroid } from '@rnef/platform-android';
import { platformIOS } from '@rnef/platform-ios';
import { pluginMetro } from '@rnef/plugin-metro';
import { zephyrMetroRNEFPlugin } from 'zephyr-metro-plugin';

export default {
  bundler: pluginMetro(),
  platforms: {
    ios: platformIOS(),
    android: platformAndroid(),
  },
  plugins: [zephyrMetroRNEFPlugin()],
};

Bundle Mini Application

Bundle your mini application for different platforms:

# React Native CLI: bundle and upload for iOS
npx react-native bundle-mf-remote --platform ios --dev false

# React Native CLI: bundle and upload for Android
npx react-native bundle-mf-remote --platform android --dev false

# RNEF: bundle and upload for iOS
rnef bundle-mf-remote --platform ios --dev false

After the first command completes, open the Zephyr dashboard and verify that the inferred or configured application appears with a new version. If no version appears, publication did not complete; Metro configuration logs alone are not deployment confirmation.

Host Application (Consumer)

Host applications load and orchestrate mini-applications:

// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withZephyr } = require('zephyr-metro-plugin');
const { withModuleFederation } = require('@module-federation/metro');

const miniAppPort = process.env.MINI_APP_PORT ?? '8082';
const useZephyrRemotes = process.env.ZEPHYR_REMOTE_RESOLUTION === '1';

const config = {
  resolver: { useWatchman: false },
};

const mfConfig = {
  name: 'hostApp',
  remotes: {
    miniApp: useZephyrRemotes
      ? 'zephyr:miniApp@yourEnvironment'
      : `miniApp@http://localhost:${miniAppPort}/mf-manifest.json`,
  },
  shared: {
    react: {
      singleton: true,
      eager: true,
      requiredVersion: '19.1.0',
      version: '19.1.0',
    },
    'react-native': {
      singleton: true,
      eager: true,
      requiredVersion: '0.80.0',
      version: '0.80.0',
    },
  },
  shareStrategy: 'loaded-first',
  runtimePlugins: [require.resolve('zephyr-native-cache/runtime-plugin')],
};

const getConfig = async () => {
  const baseConfig = mergeConfig(getDefaultConfig(__dirname), config);
  const zephyrConfig = await withZephyr({
    name: mfConfig.name,
    remotes: mfConfig.remotes,
    target: process.env.ZEPHYR_TARGET === 'android' ? 'android' : 'ios',
  })(baseConfig);

  return withModuleFederation(zephyrConfig, mfConfig, {
    flags: {
      unstable_patchHMRClient: true,
      unstable_patchInitializeCore: true,
      unstable_patchRuntimeRequire: true,
    },
  });
};

module.exports = getConfig();

Use local HTTP manifest URLs during local development. Use Zephyr selectors such as zephyr:miniApp@yourEnvironment for builds that should resolve remotes from Zephyr Cloud.

To publish the host itself, run its registered host command. Zephyr discovers Metro artifacts under dist/<platform>, so keep --bundle-output in that directory:

# React Native CLI
npx react-native bundle-mf-host --platform ios --dev false --entry-file index.js --bundle-output dist/ios/host.bundle

# RNEF
rnef bundle-mf-host --platform ios --dev false --entry-file index.js --bundle-output dist/ios/host.bundle

To enable OTA behavior, register the native cache before the host loads remote bundles:

// index.js
import ZephyrNativeCache from 'zephyr-native-cache';
import { withAsyncStartup } from '@module-federation/metro/bootstrap';
import { AppRegistry } from 'react-native';
import { name as appName } from './app.json';

ZephyrNativeCache.register({
  enablePolling: true,
  pollIntervalMs: 5 * 60 * 1000,
});

AppRegistry.registerComponent(
  appName,
  withAsyncStartup(
    () => require('./src/App'),
    () => require('./src/Fallback'),
  ),
);

For full OTA behavior, update policies, cache status APIs, and rollback behavior, see React Native OTA Updates.

Zephyr Dependencies

Configure Zephyr dependencies in your host application's package.json:

{
  "name": "hostApp",
  "version": "1.0.0",
  "zephyr:dependencies": {
    "miniApp": "zephyr:miniApp@yourEnvironment"
  }
}

For more details, see Remote Dependencies.

When building a host for Zephyr-resolved remotes, use the same selector in mfConfig.remotes and zephyr:dependencies so the Metro plugin can replace the selector with the resolved manifest URL.

Next Steps