Skip to content
metro-bundler logo

Metro Bundler Expert

metro-bundler

Metro bundler configuration, optimization, and troubleshooting. Use for bundle errors, "unable to resolve module", or caching issues.

SKILL.md

Full skill instructions

Metro Bundler Expert

Comprehensive expertise in React Native's Metro bundler, including configuration, optimization, custom transformers, caching strategies, and troubleshooting common bundling issues.

What I Know

Metro Fundamentals

What is Metro?

  • JavaScript bundler for React Native
  • Transforms and bundles JavaScript modules
  • Handles assets (images, fonts, etc.)
  • Provides fast refresh for development
  • Generates source maps for debugging

Key Concepts

  • Transformer: Converts source code (TypeScript, JSX) to JavaScript
  • Resolver: Locates modules in the file system
  • Serializer: Combines modules into bundles
  • Cache: Speeds up subsequent builds

Metro Configuration

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);

Optimization Strategies

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
    ],
  },
};

Custom Transformers

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);
    },
  },
};

Caching Strategies

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',
};

Monorepo Setup

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;
    },
  },
};

Common Issues & Solutions

"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');

When to Use This Skill

Ask me when you need help with:

  • Configuring Metro bundler
  • Custom transformers (SVG, images, etc.)
  • Optimizing bundle size and startup time
  • Setting up monorepo with Metro
  • Troubleshooting "Unable to resolve module" errors
  • Clearing Metro cache effectively
  • Configuring source maps
  • Platform-specific file resolution
  • Debugging bundling performance
  • Custom asset handling
  • Port conflicts (8081)
  • Symlink resolution in monorepos

Essential Commands

Development

# 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

Debugging

# 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

Cache Management

# 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

Pro Tips & Tricks

1. Bundle Analysis

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

2. Environment-Specific Configuration

// 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')] : []),
    ],
  },
};

3. Custom Asset Pipeline

// 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;
    },
  },
};

4. Preloading Heavy Modules

// 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);

5. Development Performance Boost

// 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,
  },
};

Integration with SpecWeave

Configuration Management

  • Document Metro configuration in docs/​internal/​architecture/
  • Track bundle size across increments
  • Include bundling optimization in tasks.md

Performance Monitoring

  • Set bundle size thresholds
  • Track startup time improvements
  • Document optimization strategies

Troubleshooting

  • Maintain runbook for common Metro issues
  • Document cache clearing procedures
  • Track bundling errors in increment reports

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View