Metro: What It Is, Key Features, and How It Works

Author: IT Sectr Published: 2026-05-19 Reading time: 8 min

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 the default bundler for React Native, optimized for mobile development on iOS and Android.
  • Three-phase architecture of Metro includes module resolution, transformation, and serialization into the output bundle.
  • Fast Refresh is a hot reload mechanism for components without losing application state.
  • Inline Requires defer module loading until their first call, reducing the initial bundle size.
  • Hermes is a JavaScript engine from Meta that uses Metro to generate precompiled bytecode.

What Is Metro?

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.

History and Positioning

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.

How Does the Metro Bundler Work?

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.

The Build Process in Metro

js
// 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 Architecture

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.

Key Metro Components

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.

Configuring Metro in a React Native Project

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.

Basic Metro Configuration for React Native

js
// 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 Build Optimization

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

Enabling Inline Requires and Hermes

js
// 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.

Delta Bundles and Incremental Build

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 and Metro: Working Together

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.

Monitoring Metro Performance

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

Can Metro be replaced with Webpack in React Native?

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.

Why doesn't Metro support CSS?

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.

How does Metro handle images and fonts?

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.

How is Metro different from Rollup?

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.

How to speed up cold Metro builds?

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

  • Metro is the standard React Native bundler, optimized for mobile platforms with limited resources.
  • Three-phase architecture (Resolve, Transform, Serialize) enables incremental builds and Fast Refresh.
  • Fast Refresh — hot reload of changed components without losing application state.
  • Inline Requires defer module loading until the first call, reducing the initial bundle by 15–30%.
  • Hermes — a JavaScript engine from Meta that compiles JS into bytecode via Metro for faster startup.
  • Watchman — a file watcher mandatory for comfortable Metro work on projects with a large number of files.
  • For monorepos, configuration of 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.

Discuss the project

Read also