In a real app
The entry point is metro.config.js (or metro.config.mts since Metro 0.87, shipped from React Native 0.87), which extends getDefaultConfig from expo/metro-config or @react-native/metro-config. A project that does not start from that default, or that copied a config from three years ago, misses everything added since: package exports resolution, monorepo support, SVG assets, and so on. The rule I apply: custom config should fit in a few lines and every line should have a comment saying why.
Resolution problems are the most frequent. A module reported missing while it sits in node_modules usually comes from the cache (npx expo start --clear), a duplicated version in a monorepo, or a package exports field Metro reads differently from Node. .native.ts and .web.ts files are the right way to split code per platform; a Platform.OS guarding a require gets in the way of tree shaking on web export.
Metro itself has moved a lot: resolution announced as around 15x faster with React Native 0.76, source map generation twice as fast and half the memory with Metro 0.87. If a project’s dev server feels slow, the first thing to check is the Metro version actually installed (npm ls metro), not the config.
Related terms
Fast Refresh
React Native’s hot reloading: a component edit is applied within a second or two while local state is preserved.
Hermes
Meta’s JavaScript engine for React Native, the default since 0.70, which precompiles the bundle to bytecode at build time.
Monorepo
One repository holding several packages linked by the package manager (workspaces); needs care around Metro, autolinking, and dependency hoisting.
React Native DevTools
React Native’s built-in debugger since 0.76, based on Chrome DevTools: console, breakpoints, network, profiling, React tree.
Expo
The open-source framework React Native recommends: a native module SDK, a CLI, prebuild, Expo Router, and the EAS cloud services.