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 @module-federation/runtime

If you use React Native CLI custom commands, install the React Native CLI command package:

npm
yarn
pnpm
bun
deno
npm add --dev @module-federation/metro-plugin-rnc-cli

If you use RNEF, install the RNEF Module Federation plugin:

npm
yarn
pnpm
bun
deno
npm add --dev @module-federation/metro-plugin-rnef

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

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 Configuration

Create or modify react-native.config.js to enable bundling with Zephyr:

// react-native.config.js
const commands = require('@module-federation/metro-plugin-rnc-cli');
const { updateManifest } = require('@module-federation/metro');
const { zephyrCommandWrapper } = require('zephyr-metro-plugin');

const wrappedFuncPromise = zephyrCommandWrapper(
  commands.bundleMFRemoteCommand.func,
  commands.loadMetroConfig,
  () => {
    updateManifest(
      global.__METRO_FEDERATION_MANIFEST_PATH,
      global.__METRO_FEDERATION_CONFIG,
    );
  },
);

const zephyrCommand = {
  name: 'bundle-mf-remote',
  description:
    'Bundles a Module Federation remote, including its container entry and all exposed modules for consumption by host applications',
  func: async (...args) => {
    const wrappedFunc = await wrappedFuncPromise;
    return wrappedFunc(...args);
  },
  options: commands.bundleMFRemoteCommand.options,
};

module.exports = {
  commands: [zephyrCommand],
};

Bundle Mini Application

Bundle your mini application for different platforms:

# Bundle for iOS
npx react-native bundle-mf-remote --platform ios --dev false

# Bundle for Android
npx react-native bundle-mf-remote --platform android --dev false

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 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