Metro Bundler is a specialized JavaScript bundler from Meta, developed exclusively for React Native and included in the framework core. According to the official React Native 0.76 documentation (2025), Metro provides incremental builds with Fast Refresh and module resolution with platform-specific extensions .native.js, .ios.js and .android.js. Unlike Webpack, Metro does not use a long-lived module graph cache — instead it rebuilds the graph each time files change, providing instant Hot Reload during development.
Key Takeaways
Metro Bundler is an open-source JavaScript bundler developed by Meta for React Native and used by default in all framework projects. Metro’s task is to take the project’s JavaScript code along with dependencies and bundle it into one or several bundles that run on the device. Unlike universal bundlers (Webpack, Rollup, Parcel), Metro is optimized for mobile development specifics: minimal build time, incremental updates during development, and correct handling of platform modules (Objective-C, Swift, Java, Kotlin native modules). Metro ships as part of React Native CLI and does not require separate installation. Starting from React Native 0.72, Metro version 0.80+ supports parallel module transpilation, speeding up builds on multi-core processors by up to 40%.
Metro’s architecture is divided into three independent components: Resolver, Transformer and Bundler. The Resolver is responsible for finding and resolving modules — it reads import/require expressions, finds the corresponding files considering platform extensions and returns absolute paths. The Transformer performs transpilation: applies Babel with presets and plugins, transforms JSX, TypeScript or Flow into plain JavaScript. The Bundler is the final stage: it takes the dependency graph from the Resolver and transformed files from the Transformer, then serializes them into one or several bundles (module bundles). The separation into three steps allows Metro to cache the results of each stage: if only one file changed, the Resolver and Bundler can use the cache for the remaining modules, providing a build speed increase of up to 10 times in development mode.
The Resolver in Metro implements a module resolution algorithm partially compatible with Node.js module resolution. The key difference from Node.js is support for platform extensions: require(‘./Component’) searches for Component.native.js, Component.ios.js, Component.android.js in the specified priority order. The Resolver also supports the package.json field “react-native” for mapping modules to alternative implementations — this is the standard mechanism for libraries with platform-specific code. If a file is not found, the Resolver throws an error with the full search stack.
The Transformer inside Metro uses Babel with the metro-react-native-babel-preset preset. The standard configuration includes: transforming JSX to React.createElement, TypeScript support (type stripping), Flow support and polyfills for modern JavaScript standards (async/await, optional chaining, nullish coalescing). The Transformer works in parallel mode: each module is transpiled independently, using a pool of worker processes (by default the number corresponds to the CPU count). Starting from Metro 0.80, Granular Transformer Cache is supported — cache invalidation at the individual file level.
One of the key features of Metro is support for platform file extensions. A React Native project can contain three versions of the same module: Component.ios.js (iOS), Component.android.js (Android) and Component.native.js (both platforms). When importing require(‘./Component’), the Resolver automatically selects the correct version depending on the target build platform. This allows writing platform-dependent code without conditional Platform.OS constructs. Priority order: name.platform.js > name.native.js > name.js. The .native.js extension is used for common code that works on both platforms but is incompatible with the web. The .ios.js and .android.js extensions serve for implementing specific features — navigation, gestures, file system operations. Metro supports custom extensions through the resolver.sourceExts configuration.
// metro.config.js — sourceExts and watchFolders setup
const config = {
resolver: {
sourceExts: ['jsx', 'js', 'tsx', 'ts', 'json'],
platformExtensions: ['ios', 'android', 'native'],
},
transformer: {
babelTransformerPath: require('metro-react-native-babel-transformer'),
},
watchFolders: [path.resolve('../shared')],
};
Fast Refresh is a hot reload mechanism for React Native built on top of Metro. When a developer changes a file, Metro re-transpiles only the changed module and sends the update to the app via WebSocket. Fast Refresh updates the UI without reloading the entire application and preserves React component state if only JSX or styles changed. If code with hooks or state is changed, Fast Refresh remounts only the changed component. Metro supports two modes: Hot Module Replacement (HMR) for on-the-fly module replacement and Live Reload for full application reload when native modules or configuration change. Fast Refresh is enabled by default in React Native 0.76 and does not require additional configuration — just run npx react-native start.
| Mode | Speed | Preserves state | Trigger |
|---|---|---|---|
| Fast Refresh | 200-500ms | Yes | JS/TS file changes |
| HMR | 100-300ms | Yes | CSS/style changes |
| Live Reload | 1-3s | No | Native config changes |
| Full Rebuild | 10-60s | No | podspec/build.gradle changes |
The metro.config.js file is the Metro configuration file located in the root of a React Native project. It configures: resolver (sourceExts, platformExtensions, extraNodeModules), transformer (babelTransformerPath, minifierConfig), server (port, host, enableFastRefresh) and watcher (watchFolders, healthCheck). The standard Metro configuration inherits from the @react-native/metro-config package (React Native 0.76+). For monorepo projects, add watchFolders — this allows Metro to track changes in packages outside the project root. To configure sourceExts, add ‘svg’, ‘png’, ‘gql’ for custom transformers. Metro supports async configurations through async function — this is useful for dynamic config generation based on the environment.
Optimization of Metro builds starts with proper cache configuration. Set maxWorkers to the number of CPUs minus 1. For distributions, use resetCache on CI. Minimize sourceExts — extra extensions slow down module resolution. For large projects, configure watchFolders only for necessary directories. Metro supports Hermes bytecode minification, which produces a smaller bundle size than standard Terser — use minifierPath: ‘metro-minify-terser’.
Metro supports a build mode directly into Hermes bytecode — skipping the JavaScript AST stage. When building with HermesTransformer, modules are compiled into HBC (Hermes ByteCode) instead of plain JavaScript. This provides: smaller bundle size (30-40%), faster application startup (20-30%) and lower memory consumption. To enable, set hermesCommand in metro.config.js and activate the Hermes flag in build.gradle (Android) or Podfile (iOS). Hermes bundle building is done in two stages: first Metro builds the JavaScript bundle, then Hermes CLI converts it to HBC. Starting from React Native 0.70, Hermes is the default engine on Android, on iOS it requires explicit enabling.
The difference between Metro and Webpack is due to different goals. Webpack is a universal bundler for the web with a huge ecosystem of plugins and loaders. Metro is a specialized bundler for React Native where the priority is incremental build speed and correct work with native modules. Webpack uses a long-lived module graph with smart cache invalidation — this provides fast rebuilds on the web but creates problems with platform extensions. Metro does not cache the graph between builds, but builds it each time — this simplifies handling of platform extensions and Haste modules. Webpack supports dynamic imports with chunks, Metro does too — through React.lazy() and Suspense, but with a limitation of one active request. Metro’s ecosystem is significantly smaller: there is no replacement for style-loader, css-loader, file-loader — these tasks are solved differently in React Native.
Metro is the only correct choice for React Native projects. Webpack does not support platform-aware resolution out of the box, and setting up React Native with Webpack requires significant effort (haul bundler, expo web packager). Metro is integrated into React Native CLI, supported by the official Meta team and receives updates with every React Native release. For React Native Web projects, you can use Webpack for web builds while keeping Metro for mobile bundles.
Frequently Asked Questions
Run npx react-native start --reset-cache or delete the $TMPDIR/metro-* folder and the .metro-health-check* directory in the project root. Alternatively: npx react-native clean — this command clears all Metro temporary files, including Babel cache and Haste map. After clearing, the first build will be a full build (60-120 seconds), but subsequent incremental builds will return to normal speed.
Check that the package is in package.json dependencies (not devDependencies). If the module is in a monorepo, add the path to watchFolders in metro.config.js. Metro does not follow symlinks by default — use resolver.extraNodeModules for explicit mapping. For Yarn PnM, set resolver.useWatchman: false. If the module uses platform extensions, make sure that .ios.js or .android.js files exist at the specified path.
Increase maxWorkers in metro.config.js to the number of CPUs minus 1. Configure watchFolders only for necessary directories. Use Hermes bytecode for production builds — this speeds up the final build by skipping JS minification. For development, enable Fast Refresh (it is enabled by default). Limit sourceExts to only necessary extensions. Consider splitting the bundle into chunks through lazy loading.
Technically yes, but it is not recommended. Metro is not optimized for web builds: there is no support for CSS, HTML, images as modules, no code splitting with dynamic imports at the browser level. For the web, use Webpack, Vite or Parcel. Metro is a specialized solution for React Native, and attempting to adapt it for the web will lead to performance loss and lack of a loader ecosystem.
Re.Pack is a community tool for replacing Metro with Webpack in React Native projects. It provides access to the Webpack loader ecosystem (css-loader, svg-loader) and advanced code splitting. However, Re.Pack is more complex to configure, is not officially supported by Meta, and does not guarantee compatibility with new versions of React Native. Metro remains the only officially supported bundler, providing stability and guaranteed compatibility with every release.
Summary
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