skip to content

After switching git branches, a React Native app fails with Metro's 'Unable to resolve module' error. How do you triage it?

level: middleimportance: must knowfreq 55%

answer

  1. read which module and from which file
  2. package: node_modules did not switch
  3. relative file: exists on this branch?
  4. stale file map: restart, then reset
  5. watchman watch-del-all as escalation

basics

~20 s

Read which module failed and which file imported it. A missing package usually means node_modules still matches the old branch, so reinstall. A missing relative file is a real code change. If the file exists, restart Metro, then reset caches.

solid answer

~50 s

Start from the message: Metro says `Unable to resolve module X from Y`, and either lists the file candidates it tried (`None of these files exist`) or says the package could not be found in the project or its `node_modules` folders. If X is a **package**, the usual cause is that `node_modules` did not change with the branch: git does not track it, so run your package manager's install. If X is a **relative path**, check whether the file exists on this branch and whether the importing file is itself stale. If the file is there and Metro still fails, Metro's **file map** is out of date: a dev server kept running through a large checkout, or a watcher that missed events. Restart Metro, then `npx react-native start --reset-cache` or `npx expo start --clear`, then `watchman watch-del-all`. Only after that suspect resolver configuration.

code

bash · 8 lines
bash
# 1. Branch changed dependencies? Reinstall (use the project's package manager)
npm install

# 2. File exists but Metro disagrees? Restart clean
npx react-native start --reset-cache   # or: npx expo start --clear

# 3. Still stale? Reset Watchman's watches, then start again
watchman watch-del-all

go deeper

for a junior

Read the error's module and importing file, reinstall dependencies after switching branches, and restart Metro before anything else.

for a middle

Separate missing packages, missing relative files and a stale file map, and know which command fixes each.

for a senior

Run the escalation in order, restart, cache reset, Watchman, reinstall, and only then compare resolver or Babel alias configuration between branches.

for a principal

Reduce the whole class of errors with team habits: automatic installs on lockfile changes and a documented triage order instead of ad-hoc resets.

## The scenario You switch from `main` to a colleague's feature branch, reload the app, and the red screen says: ``` Unable to resolve module @scope/charts from /app/src/screens/Stats.tsx: @scope/charts could not be found within the project or in these directories: node_modules ``` or, for a relative import: ``` Unable to resolve module ./StatsCard from /app/src/screens/Stats.tsx: None of these files exist: * src/screens/StatsCard(.ios.js|.native.js|.js|...|.ios.tsx|.native.tsx|.tsx) * src/screens/StatsCard ``` The message tells you **what** could not be found and **who** asked for it. That is enough to pick the branch of the triage. ## Step 1: a package that cannot be found Git does not track `node_modules`. When the feature branch adds a dependency, its `package.json` and lockfile change, but the installed packages still belong to the previous branch. Metro reports the package as missing from `node_modules`, and it is right. 1. Run the project's install command (`npm install`, `yarn`, `pnpm install` or `bun install`). 2. If the dependency has **native code**, rebuild the app as well; resolving the JavaScript is not enough. 3. Restart Metro. This is the most common cause after a branch switch, and no cache reset fixes it. ## Step 2: a relative file that cannot be found 1. Check that the file exists at that path **on this branch**. Renames and moves are frequent in feature branches. 2. Check the importing file: if the other branch changed an import and this branch did not, the error is a genuine code problem, not a cache problem. 3. Watch for platform files: the candidate list shows Metro tried platform suffixes such as `.ios.tsx`, `.native.tsx` and plain `.tsx`. A file that exists only as `.android.tsx` fails when you bundle for iOS. ## Step 3: the file exists but Metro disagrees Now the cache is the suspect. Metro resolves against its **file map**, an in-memory index of project files, persisted to disk so restarts are fast. A running dev server updates it from file-system events, delivered by **Watchman** when installed and otherwise by Node's own watcher. A checkout that rewrites thousands of files at once can leave the map behind, especially if the watcher had to recrawl. Metro's Watchman integration can defer processing while a source-control operation runs, but its default deferred state, `hg.update`, is Mercurial's, so a git checkout gets no such pause by default. Escalate in this order and stop as soon as the error goes: 1. **Restart Metro.** A fresh start rebuilds the file map against the disk. 2. **Reset Metro's cache:** `npx react-native start --reset-cache` or `npx expo start --clear`. This discards both the transform cache and the persisted file map. 3. **Reset Watchman:** `watchman watch-del-all`, then start Metro again. 4. **Reinstall dependencies** from scratch if the error mentions a package inside `node_modules`. 5. As a last resort, delete Metro's temporary files: `rm -rf ${TMPDIR:-/tmp}/metro-*`. ## Step 4: only now suspect configuration If the error survives a clean start with the file present, the problem is resolution rules: an alias defined by a Babel plugin or `resolveRequest` that differs between branches, a `blockList` pattern hiding the folder, or a file outside `projectRoot` and `watchFolders`. Compare `metro.config.js` and `babel.config.js` between the two branches. A Babel alias change in a bare React Native app also needs a cache reset, because its Babel config is not part of the transform cache key. ## A quick decision table | Error detail | Most likely cause | First action | |---|---|---| | package not found in `node_modules` | dependencies from the old branch | install, then restart Metro | | relative file, file missing on disk | the branch renamed or removed it | fix the import or file | | relative file, file present | stale file map | restart, then reset cache | | survives a clean start | resolver or alias config | compare configs between branches | ## Habits that prevent it - Stop Metro before large checkouts, or restart it right after. - Run the install command after every branch switch that touches the lockfile; a git hook can do it automatically. - Resist resetting everything first: reading the message usually points straight at the cause and saves a slow cold start.

  • Why does the error often mention a package even though the code on the branch is correct?
    The branch added a dependency to `package.json`, but `node_modules` is not tracked by git and still holds the previous branch's packages. Metro correctly reports the package as missing until you run the install command, and a native dependency also needs an app rebuild.
  • What is Watchman's role in these errors?
    Watchman reports file-system changes to Metro so its file map stays current while the dev server runs. If its watch falls behind or is corrupted, Metro can believe a file is missing. Restarting Metro rebuilds the map; `watchman watch-del-all` clears Watchman's own state when that is not enough.

saying these in an interview costs you the question

  • Always reset Metro's cache first, whatever the error says
  • git checkout also switches the installed node_modules
  • --reset-cache installs dependencies the new branch added
  • If the file exists on disk, Metro must be able to resolve it
  • Watchman pauses Metro automatically during every git checkout