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.
Quick Setup with Codemod
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 add --dev zephyr-metro-plugin @module-federation/metro@^2.9.0 @module-federation/runtime
yarn add --dev zephyr-metro-plugin @module-federation/metro@^2.9.0 @module-federation/runtime
pnpm add --dev zephyr-metro-plugin @module-federation/metro@^2.9.0 @module-federation/runtime
bun add --dev zephyr-metro-plugin @module-federation/metro@^2.9.0 @module-federation/runtime
deno add --dev npm:zephyr-metro-plugin npm:@module-federation/metro@^2.9.0 npm:@module-federation/runtime
To enable React Native OTA updates in a host app, also install the native cache runtime:
npm add zephyr-native-cache
yarn add zephyr-native-cache
pnpm add zephyr-native-cache
bun add zephyr-native-cache
deno add npm:zephyr-native-cache
Configuration and Publication
Metro configuration and publication are separate stages:
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