Expert in Metro bundler configuration, optimization, troubleshooting, caching strategies, custom transformers, asset management, source maps, bundling performance. Activates for metro, metro bundler, metro.config.js, bundler, bundle, cache, transformer, asset resolver, metro cache, bundling error, unable to resolve module, port 8081.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Comprehensive expertise in React Native's Metro bundler, including configuration, optimization, custom transformers, caching strategies, and troubleshooting common bundling issues.
What is Metro?
Key Concepts
Basic metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = config;
Custom Configuration
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
const config = {
transformer: {
// Enable Babel transformer
babelTransformerPath: require.resolve('react-native-svg-transformer'),
// Source map options
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
// Custom asset extensions
assetExts: defaultConfig.resolver.assetExts.filter(ext => ext !== 'svg'),
// Custom source extensions
sourceExts: [...defaultConfig.resolver.sourceExts, 'svg', 'cjs'],
// Node module resolution
nodeModulesPaths: [
'./node_modules',
'../../node_modules', // For monorepos
],
// Custom platform-specific extensions
platforms: ['ios', 'android', 'native'],
},
server: {
// Custom port
port: 8081,
// Enhanced logging
enhanceMiddleware: (middleware) => {
return (req, res, next) => {
console.log(`Metro request: ${req.url}`);
return middleware(req, res, next);
};
},
},
watchFolders: [
// Watch external folders (monorepos)
path.resolve(__dirname, '..', 'shared-library'),
],
resetCache: true, // Reset cache on start (dev only)
};
module.exports = mergeConfig(defaultConfig, config);
Inline Requires
// metro.config.js
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
inlineRequires: true, // Lazy load modules (faster startup)
},
}),
},
};
// Before (eager loading)
import UserProfile from './UserProfile';
import Settings from './Settings';
function App() {
return (
<View>
{showProfile ? <UserProfile /> : <Settings />}
</View>
);
}
// After inline requires (lazy loading)
function App() {
return (
<View>
{showProfile ?
<require('./UserProfile').default /> :
<require('./Settings').default />
}
</View>
);
}
Bundle Splitting (Experimental)
// metro.config.js
module.exports = {
serializer: {
createModuleIdFactory: () => {
// Generate stable module IDs for better caching
return (path) => {
return require('crypto')
.createHash('sha1')
.update(path)
.digest('hex')
.substring(0, 8);
};
},
},
};
Asset Optimization
// metro.config.js
module.exports = {
transformer: {
// Minify assets
minifierPath: require.resolve('metro-minify-terser'),
minifierConfig: {
compress: {
drop_console: true, // Remove console.log in production
drop_debugger: true,
},
output: {
comments: false,
},
},
},
resolver: {
// Optimize asset resolution
assetExts: [
'png', 'jpg', 'jpeg', 'gif', 'webp', // Images
'mp3', 'wav', 'm4a', 'aac', // Audio
'mp4', 'mov', // Video
'ttf', 'otf', 'woff', 'woff2', // Fonts
],
},
};
SVG Transformer
# Install
npm install react-native-svg react-native-svg-transformer
# metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.transformer = {
...config.transformer,
babelTransformerPath: require.resolve('react-native-svg-transformer'),
};
config.resolver = {
...config.resolver,
assetExts: config.resolver.assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...config.resolver.sourceExts, 'svg'],
};
module.exports = config;
// Usage in code
import Logo from './assets/logo.svg';
function App() {
return <Logo width={120} height={40} />;
}
Multiple File Extensions
// metro.config.js
module.exports = {
resolver: {
// Add .web.js, .native.js for platform-specific code
sourceExts: ['js', 'json', 'ts', 'tsx', 'jsx', 'web.js', 'native.js'],
// Custom resolution logic
resolveRequest: (context, moduleName, platform) => {
if (moduleName === 'my-module') {
// Custom module resolution
return {
filePath: '/custom/path/to/module.js',
type: 'sourceFile',
};
}
return context.resolveRequest(context, moduleName, platform);
},
},
};
Cache Management
# Clear Metro cache
npx react-native start --reset-cache
npm start -- --reset-cache # Expo
# Or manually
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
# Clear watchman cache
watchman watch-del-all
# Clear all caches (nuclear option)
npm run clear # If configured in package.json
Cache Configuration
// metro.config.js
const path = require('path');
module.exports = {
cacheStores: [
// Custom cache directory
{
get: (key) => {
const cachePath = path.join(__dirname, '.metro-cache', key);
// Implement custom cache retrieval
},
set: (key, value) => {
const cachePath = path.join(__dirname, '.metro-cache', key);
// Implement custom cache storage
},
},
],
// Reset cache on config changes
resetCache: process.env.RESET_CACHE === 'true',
};
Workspaces Configuration
// metro.config.js (in app directory)
const path = require('path');
const { getDefaultConfig } = require('@react-native/metro-config');
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '../..');
const config = getDefaultConfig(projectRoot);
// Watch workspace directories
config.watchFolders = [workspaceRoot];
// Resolve modules from workspace
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
];
// Avoid hoisting issues
config.resolver.disableHierarchicalLookup = false;
module.exports = config;
Symlink Handling
// metro.config.js
module.exports = {
resolver: {
// Enable symlink support
unstable_enableSymlinks: true,
// Resolve symlinked packages
resolveRequest: (context, moduleName, platform) => {
const resolution = context.resolveRequest(context, moduleName, platform);
if (resolution && resolution.type === 'sourceFile') {
// Resolve real path for symlinks
const realPath = require('fs').realpathSync(resolution.filePath);
return {
...resolution,
filePath: realPath,
};
}
return resolution;
},
},
};
"Unable to resolve module"
# Solution 1: Clear cache
npx react-native start --reset-cache
# Solution 2: Reinstall dependencies
rm -rf node_modules
npm install
# Solution 3: Check import paths
# Ensure case-sensitive imports match file names
import UserProfile from './userProfile'; # ❌ Wrong case
import UserProfile from './UserProfile'; # ✅ Correct
# Solution 4: Add to metro.config.js
module.exports = {
resolver: {
extraNodeModules: {
'my-module': path.resolve(__dirname, 'node_modules/my-module'),
},
},
};
"Port 8081 already in use"
# Find and kill process
lsof -ti:8081 | xargs kill -9
# Or start on different port
npx react-native start --port 8082
# Update code to use new port
adb reverse tcp:8082 tcp:8082 # Android
"Invariant Violation: Module AppRegistry is not a registered callable module"
# Clear all caches
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
watchman watch-del-all
rm -rf node_modules
npm install
npx react-native start --reset-cache
"TransformError: ... SyntaxError"
// Add Babel plugin to metro.config.js
module.exports = {
transformer: {
babelTransformerPath: require.resolve('./customBabelTransformer.js'),
},
};
// customBabelTransformer.js
module.exports = require('metro-react-native-babel-preset');
Ask me when you need help with:
# Start Metro bundler
npx react-native start
# Start with cache cleared
npx react-native start --reset-cache
# Start with custom port
npx react-native start --port 8082
# Start with verbose logging
npx react-native start --verbose
# Expo dev server
npx expo start
# Expo with cache cleared
npx expo start -c
# Check Metro status
curl http://localhost:8081/status
# Get bundle (for debugging)
curl http://localhost:8081/index.bundle?platform=ios > bundle.js
# Check source map
curl http://localhost:8081/index.map?platform=ios > bundle.map
# List all modules in bundle
curl http://localhost:8081/index.bundle?platform=ios&dev=false&minify=false
# Clear Metro cache
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
# Clear watchman
watchman watch-del-all
# Clear all (comprehensive)
npm run clear # Custom script
# Or manually:
rm -rf $TMPDIR/react-*
rm -rf $TMPDIR/metro-*
watchman watch-del-all
rm -rf node_modules
npm install
Analyze bundle size to find optimization opportunities:
# Generate bundle with source map
npx react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--bundle-output ./bundle.js \
--sourcemap-output ./bundle.map
# Analyze with source-map-explorer
npm install -g source-map-explorer
source-map-explorer bundle.js bundle.map
// metro.config.js
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
transformer: {
minifierConfig: {
compress: {
drop_console: !isDev, // Remove console.log in production
},
},
},
serializer: {
getModulesRunBeforeMainModule: () => [
// Polyfills for production
...(!isDev ? [require.resolve('./polyfills.js')] : []),
],
},
};
// metro.config.js
module.exports = {
transformer: {
// Optimize images during bundling
assetPlugins: ['expo-asset/tools/hashAssetFiles'],
},
resolver: {
assetExts: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'],
// Custom asset resolution
resolveAsset: (dirPath, assetName, extension) => {
const basePath = `${dirPath}/${assetName}`;
// Try @2x, @3x variants
const variants = ['@3x', '@2x', ''];
for (const variant of variants) {
const path = `${basePath}${variant}.${extension}`;
if (require('fs').existsSync(path)) {
return path;
}
}
return null;
},
},
};
// index.js
import { AppRegistry } from 'react-native';
import App from './App';
// Preload heavy modules
import('./src/heavyModule').then(() => {
console.log('Heavy module preloaded');
});
AppRegistry.registerComponent('MyApp', () => App);
// metro.config.js
const isDev = process.env.NODE_ENV !== 'production';
module.exports = {
transformer: {
// Skip minification in dev
minifierPath: isDev ? undefined : require.resolve('metro-minify-terser'),
// Faster source maps in dev
getTransformOptions: async () => ({
transform: {
inlineRequires: !isDev, // Only in production
},
}),
},
server: {
// Increase file watching performance
watchFolders: isDev ? [] : undefined,
},
};
Configuration Management
docs/internal/architecture/tasks.mdPerformance Monitoring
Troubleshooting