Metro is a JavaScript bundler developed specifically for React Native and used by default for building mobile applications on this platform. Unlike general-purpose bundlers, Metro is optimized for the limited resources of mobile devices and provides integration with the Hermes engine and Fast Refresh. According to Metro Documentation (2026), the bundler handles over 90% of React Native projects worldwide.
Key Takeaways
Metro is an open-source JavaScript bundler created by the Meta team for React Native. It replaced the Packager from earlier versions of React Native and became the standard build tool for this platform. Metro is not designed for web development: its architecture is focused exclusively on mobile applications running on JSC (JavaScriptCore) or Hermes JavaScript engines.
Metro was introduced in 2018 as a replacement for the old React Native Packager. The main reason for its creation was the need for a bundler that efficiently works with millions of modules in large mobile applications. Unlike Webpack, Metro does not support CSS, HTML, or images as modules — these resources are handled by separate React Native tools. Facebook uses Metro to build its main application, which contains over 100 thousand files.
Metro is distributed via npm as the metro package and is installed automatically with React Native CLI. The Metro version is tied to the React Native version, so updating the bundler happens together with the platform update.
Metro takes the application entry point as input, builds a dependency graph, transforms each module, and serializes the result into a single bundle. The process goes through three phases: Resolve, Transform, and Serialize.
// Example Metro configuration for custom transformation
const metroConfig = {
transformer: {
babelTransformerPath: require('metro-babel-transformer'),
async transform({ src, filename, options }) {
const result = await babelTransform({ src, filename, options });
return {
ast: result.ast,
code: result.code,
map: result.map,
dependencies: [],
};
},
},
serializer: {
createModuleIdFactory() {
let nextId = 0;
const moduleIds = new Map();
return ({ path }) => {
if (!moduleIds.has(path)) {
moduleIds.set(path, nextId++);
}
return moduleIds.get(path);
};
},
},
};
During the Resolve phase, Metro determines the location of each imported module, taking into account aliases and node_modules. During the Transform phase, each file goes through a Babel transformer to convert JSX, TypeScript, and other extensions into pure JavaScript. The final Serialize phase collects all transformed modules into one or more bundle files.
Metro is built on a modular architecture where each build stage is implemented as a separate component. This allows replacing standard modules with custom ones without changing the bundler core. The Metro architecture includes three main packages: metro, metro-config, and metro-resolver.
Metro Server — a dev server that starts with the npx react-native start command. It serves real-time module transformation requests, enabling Fast Refresh. Module Store — a cache that stores transformed modules in memory to speed up rebuilds. Dependency Graph — a dependency graph that updates incrementally when files change.
Watchman — a file watcher from Meta that Metro uses to detect changes in the file system. Without Watchman, Metro has to rescan the entire file structure on every change, which significantly slows down development. Installing Watchman is mandatory for comfortable work with Metro on projects of any size.
Configuration for Metro is set in the metro.config.js file in the project root. A typical file defines additional folders for module search, blocklists for excluding unnecessary files, and custom transformers. In React Native 0.72+, auto-detection of configuration is used, but for monorepos, configuration is mandatory.
// metro.config.js
const config = {
resolver: {
sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json'],
nodeModulesPaths: ['node_modules'],
blockList: [/\.test\.js$/, /__tests__\/.*/],
extraNodeModules: {
'shared-components': path.resolve(__dirname, '../shared/src'),
},
},
transformer: {
minifierConfig: {
keep_classnames: true,
keep_fnames: true,
mangle: { reserved: ['React', 'Component'] },
},
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
blockList excludes test files from the build, reducing bundle size. nodeModulesPaths specifies additional paths for package lookup — critical for monorepos. extraNodeModules creates aliases for shared packages used across different projects in a monorepo.
Metro provides several mechanisms for optimizing bundle size and build speed. Inline Requires — the most effective technique, converting top-level imports into local require calls inside functions. RAM bundles allow loading modules incrementally. Hermes compiles JavaScript into bytecode, reducing application size by 20–30%.
// metro.config.js — production optimization
const config = {
transformer: {
async transform({ src, filename, options }) {
const inlineRequires = options.dev ? false : true;
return await defaultTransform({ src, filename, options: { ...options, inlineRequires } });
},
},
serializer: {
polyfillModuleNames: [],
},
};
// To enable Hermes — in build.gradle (Android):
// project.ext.react = [enableHermes: true, bundleInRelease: true]
// For iOS — in Podfile: :hermes_enabled => true
For bundle size diagnostics, Metro provides the --bundle-output flag with statistics output. Use bundle-visualizer to analyze bundle composition and find large modules that can be lazy-loaded. Regular bundle size checking should be part of the React Native project CI/CD pipeline.
Metro supports delta bundles — a mechanism where, after the first full build, the server sends only the changes (delta) between the old and new bundle versions. This radically speeds up subsequent builds: update time drops from seconds to tens of milliseconds. Delta bundles are especially effective during development when the developer frequently saves changes and reloads the application.
To enable delta bundles in React Native, use the --delta flag with the npx react-native bundle command. On the client side, delta bundles are supported starting from React Native 0.64. In production builds, delta bundles are not used — instead, a full bundle with Hermes bytecode is used for maximum startup performance. This is achieved because the development speed gain compensates for the initial full build cost.
Hermes is a JavaScript engine developed by Meta specifically for React Native. Metro generates Hermes bytecode at build time, allowing the application to start without expensive JavaScript compilation on the device. To enable Hermes, simply add enableHermes: true to metro.config.js and configure build.gradle or Podfile. Hermes reduces application startup time by 30–50% and reduces APK size by 20–30% compared to JavaScriptCore.
When using Hermes, it is important to remember its limitations: the engine does not support Proxy, Reflect, and some ES6 features. Most React Native applications do not use these features directly, but some libraries may conflict. Before enabling Hermes, check the compatibility of all project dependencies through the official compatibility checklist. Metro automatically switches to bytecode generation mode when Hermes is activated.
To track Metro build efficiency, use the built-in metrics available through Flipper — the React Native debugging tool. Metro publishes events: bundle_request, transform, resolve with execution time for each stage. Analyzing this data helps identify bottlenecks: if the transform phase takes more than 70% of the time, the issue is in the Babel transformer. Enable Metro logging through the --verbose flag for detailed diagnostics.
Frequently Asked Questions
Technically yes — there are experimental projects like react-native-webpack, but they are not officially supported. Metro is integrated with the React Native bridge, Turbo Modules, and Hermes at a level that is not available for Webpack. Replacing Metro would result in losing Fast Refresh and official support.
React Native does not use CSS for styling — instead, JavaScript styling is applied through StyleSheet.create. Since Metro is built exclusively for React Native, CSS support is not needed. For web rendering via React Native Web, CSS is handled by separate tools outside of Metro.
Metro does not process images or fonts as modules. Resources are imported via require('./image.png'), but Metro only registers them as string dependencies. The actual image loading is performed by native React Native code through the Image component, and Metro copies the resources into the bundle.
Rollup generates ES modules with module-level tree shaking, resulting in minimal bundle size for libraries. Metro generates a CommonJS bundle with inline transformation and optimization for mobile engines. Rollup does not support Fast Refresh and cannot work with React Native native modules.
Cold Metro builds are slow due to full node_modules scanning. Solutions: add watchFolders for monorepos, use maxWorkers for parallel transformation, and install Watchman. For projects with 10,000+ files, consider metro-memory-fs for in-memory caching.
Summary
watchFolders and extraNodeModules is required for correct module resolution.We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also