Metro is the JavaScript bundler for React Native. It takes all your JavaScript code and dependencies and bundles them into a single file that can be loaded by your application.
Overview
Metro is configured through metro.config.js in your project root. React Native provides sensible defaults through the @react-native/metro-config package.
Basic Configuration
Create or modify metro.config.js in your project root:
const { getDefaultConfig , mergeConfig } = require ( '@react-native/metro-config' );
const config = {};
module . exports = mergeConfig ( getDefaultConfig ( __dirname ), config );
Default Configuration
The @react-native/metro-config package provides these defaults:
Resolver Configuration
resolver : {
resolverMainFields : [ 'react-native' , 'browser' , 'main' ],
platforms : [ 'android' , 'ios' ],
unstable_conditionNames : [ 'react-native' ],
}
Server Configuration
server : {
port : Number ( process . env . RCT_METRO_PORT ) || 8081 ,
}
transformer : {
allowOptionalDependencies : true ,
assetRegistryPath : 'react-native/Libraries/Image/AssetRegistry' ,
babelTransformerPath : '@react-native/metro-babel-transformer' ,
getTransformOptions : async () => ({
transform: {
experimentalImportSupport: false ,
inlineRequires: true ,
},
}),
}
Common Configurations
Adding Custom File Extensions
const config = {
resolver: {
sourceExts: [ 'jsx' , 'js' , 'ts' , 'tsx' , 'json' ],
},
};
Configuring Asset Extensions
const config = {
resolver: {
assetExts: [ 'png' , 'jpg' , 'jpeg' , 'gif' , 'webp' , 'svg' ],
},
};
Adding Watch Folders (Monorepos)
const path = require ( 'path' );
const config = {
watchFolders: [
path . resolve ( __dirname , '../shared-package' ),
],
};
const config = {
transformer: {
babelTransformerPath: require . resolve ( './customTransformer.js' ),
},
};
Advanced Features
Inline Requires
Inline requires improve startup performance by lazily loading modules:
transformer : {
getTransformOptions : async () => ({
transform: {
inlineRequires: true ,
},
}),
}
Before (without inline requires):
const MyComponent = require ( './MyComponent' );
function App () {
return < MyComponent /> ;
}
After (with inline requires):
function App () {
const MyComponent = require ( './MyComponent' );
return < MyComponent /> ;
}
Custom Resolver
const config = {
resolver: {
resolveRequest : ( context , moduleName , platform ) => {
// Custom resolution logic
if ( moduleName === 'my-custom-module' ) {
return {
filePath: path . resolve ( __dirname , 'custom-path.js' ),
type: 'sourceFile' ,
};
}
return context . resolveRequest ( context , moduleName , platform );
},
},
};
Source Map Configuration
const config = {
serializer: {
createModuleIdFactory : () => {
return ( path ) => {
// Generate stable module IDs
return path ;
};
},
},
};
Symbolicator Configuration
Metro includes a symbolicator for stack trace processing:
const config = {
symbolicator: {
customizeFrame : ( frame ) => {
const collapse = Boolean (
frame . file != null && /node_modules/ . test ( frame . file )
);
return { collapse };
},
},
};
This collapses node_modules frames in LogBox for cleaner error displays.
Caching
Metro caches transformed files for faster subsequent builds:
const config = {
cacheStores: [
new FileStore ({
root: path . join ( __dirname , '.metro-cache' ),
}),
],
};
Parallel Processing
const config = {
maxWorkers: 4 , // Number of worker threads
};
Tree Shaking
Tree shaking is experimental in React Native. Test thoroughly when enabling.
transformer : {
getTransformOptions : async () => ({
transform: {
experimentalImportSupport: true ,
},
}),
}
Metro automatically resolves platform-specific files:
MyComponent.js // Fallback
MyComponent.ios.js // iOS-specific
MyComponent.android.js // Android-specific
Configuration:
resolver : {
platforms : [ 'ios' , 'android' , 'web' ],
}
Asset Handling
Image Resolution
Metro handles image resolution automatically:
Custom Asset Plugins
const config = {
transformer: {
assetPlugins: [ 'my-custom-asset-plugin' ],
},
};
Monorepo Support
Yarn Workspaces
const path = require ( 'path' );
const fs = require ( 'fs' );
// Get workspace packages
const workspaces = require ( './package.json' ). workspaces . packages ;
const config = {
watchFolders: workspaces . map ( workspace =>
path . resolve ( __dirname , '../' , workspace )
),
resolver: {
nodeModulesPaths: [
path . resolve ( __dirname , 'node_modules' ),
path . resolve ( __dirname , '../node_modules' ),
],
},
};
Environment Variables
Metro Port
# Change Metro port
RCT_METRO_PORT = 8088 npx react-native start
Reset Cache
npx react-native start --reset-cache
Troubleshooting
Clear Metro Cache
rm -rf $TMPDIR /metro- * && rm -rf $TMPDIR /haste- *
Common Issues
Error: Unable to resolve module
This usually means Metro can’t find the file:
Check the import path is correct
Verify the file exists
Add the extension to resolver.sourceExts
Check watchFolders for monorepos
Clear Metro cache
Metro crashes or becomes unresponsive
Try these solutions:
Clear Metro cache: npx react-native start --reset-cache
Reduce maxWorkers in config
Check for circular dependencies
Update Node.js to latest LTS version
Increase Node memory: NODE_OPTIONS=--max_old_space_size=4096
Optimize Metro performance:
Enable inlineRequires transformer option
Use watchFolders only when necessary
Exclude unnecessary folders from watching
Increase maxWorkers for parallel processing
Use SSD for faster file system access
Metro CLI Commands
# Start Metro bundler
npx react-native start
# Reset cache
npx react-native start --reset-cache
# Custom port
npx react-native start --port 8088
# Verbose output
npx react-native start --verbose
Next Steps
Fast Refresh Learn about Fast Refresh feature
Debugging Debug your React Native apps