◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-05-03Beginner

5 Things to Check First When Rork Shows 'Unable to Resolve Module'

Walk through the five most common causes of the 'Unable to resolve module' error in Rork, React Native, and Expo projects, with the exact commands and the order in which to check them.

Troubleshooting43Metro4React Native239Expo219Beginner7

You ask Rork to add a new screen, run npx expo start, and the dev server greets you with a red screen:

Unable to resolve module xxx from /path/to/file.tsx

It looks intimidating the first few times, but the underlying causes fall into a small handful of buckets. After hitting this error many times while iterating on Rork-generated code, I've settled on a fixed checking order that gets me back to writing features within minutes. Here it is, with the exact commands I keep in muscle memory.

Why this error happens

Metro, the React Native bundler, walks every import statement in your code to build the dependency graph for your app. The moment it can't locate a file or package referenced by an import, it stops and throws "Unable to resolve module."

A useful detail to internalize: Metro reports both missing npm packages and missing local files with the same message. Whether the failing import is import { Camera } from "expo-camera" or import { Header } from "../components/Header", the error text looks identical. The path that comes after from in the error message is your starting point for narrowing down the cause—open that file first and look at the import line that doesn't resolve.

Another thing worth knowing: Metro runs entirely independently from TypeScript. Even if tsc is happy with your imports, Metro can still fail. Treating "the type checker passes" as "the bundle will succeed" is a frequent source of false confidence, especially in projects where Rork has just added a new dependency.

1. The package isn't installed

This is by far the most common case—and a natural side effect of how Rork works. The AI generated code referencing a library that isn't yet in your package.json. Because the AI is shaping your codebase based on the prompt rather than auditing your dependency list, new imports often show up faster than the corresponding installs.

# What the error looks like
Unable to resolve module react-native-reanimated from src/screens/Home.tsx
# The fix
npx expo install react-native-reanimated

The key here is using npx expo install instead of plain npm install. The Expo CLI picks the version that matches your installed Expo SDK, which prevents a whole class of follow-up build errors. Packages that Rork tends to pull in—expo-image, expo-blur, react-native-svg, react-native-reanimated, expo-haptics—are usually one command away once you spot which one is missing.

If you're not sure which package is missing, the import statement itself is the answer. Look at the file mentioned in the error, find the import for the unresolved module name, and run npx expo install against that exact name. For scoped packages (like @react-native-async-storage/async-storage), include the scope.

2. Metro's cache is stale

If the package is in package.json and clearly installed, but the error still shows up, Metro is almost certainly serving you a stale cache. This commonly happens right after adding a dependency or switching git branches.

# Clear the cache and restart
npx expo start -c
 
# If that doesn't fix it, also wipe the deeper caches
rm -rf node_modules/.cache
watchman watch-del-all
npx expo start -c

The -c flag clears Metro's transformer and haste-map caches in one go. After any large rewrite by the Rork AI, I've made it a habit to start with -c for the first run. It saves a surprising amount of "wait, why isn't this working?" time.

If you're on a CI machine or a hosted build like EAS, the equivalent is to invalidate the build cache from the Expo dashboard or to bump a version field that triggers a fresh install. Metro caches don't follow code through CI in the way local caches do, so cache problems there usually mean stale node_modules, not stale Metro state.

3. Typos and case mismatches in the import path

Most macOS dev machines run on a case-insensitive filesystem, but EAS Build, CI, and parts of the iOS simulator's caches are case-sensitive. The classic symptom: it works on your laptop, then fails the moment you submit a build.

// Bad — file is Header.tsx but imported in lowercase
import { Header } from "./components/header";
 
// Good — exact case match
import { Header } from "./components/Header";

Extension mistakes happen too. Maybe you wrote ./utils/format thinking it pointed at a folder's index.ts, when in fact there's a file called format.ts. Use the path printed after from as your starting line, and reopen the importing file via your editor's go-to-definition. Mismatches usually become obvious within seconds.

A small habit that helps: when Rork generates a new file, glance at the import section before running it. If you see imports that don't match what you'd type by hand—weird casing, mixed quotes, missing extensions—fix them up front rather than waiting for Metro to surface the issue.

4. You forgot to teach the path alias to Babel/Metro

If you set "paths" in tsconfig.json so you can write things like @/components/Button, TypeScript will stop complaining—but Metro won't know about the alias unless you also configure Babel. TypeScript and Metro use entirely separate path resolution systems, and forgetting one is a textbook cause of the resolve error.

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "module-resolver",
        {
          root: ["./"],
          alias: {
            "@": "./src",
          },
        },
      ],
    ],
  };
};

After installing babel-plugin-module-resolver, restart Metro with the cache flag. Just adding the plugin without a fresh cache won't fix it because the old transformations are still being served.

If you'd rather avoid maintaining the Babel side of this configuration, an alternative is to keep all imports relative and skip the alias entirely. With a tidy folder structure, ../components/Button is rarely worse than @/components/Button in practice, and you cut out an entire failure mode.

5. You're running a native module on Expo Go

Libraries like react-native-vision-camera and react-native-mmkv ship native code that Expo Go doesn't have bundled. Metro tries to resolve them, fails to find the prebuilt binary, and throws this error. This is one of the trickier cases because the error message looks like a missing dependency, but installing the package won't fix it.

The fix is to switch to a Development Build:

# Create a development build
npx expo prebuild --clean
npx expo run:ios   # or run:android

You can also tell Rork to regenerate the screen with a "no native modules" constraint. In practice I prefer staying on Expo Go for the prototyping phase and only flipping to a Development Build once the feature scope is locked in—it keeps the iteration loop fast and the friction of fresh native builds out of the way until you actually need them.

A quick way to check if a library needs a Development Build is to look at its README. Anything that says "Custom Development Client required" or mentions iOS/Android-specific setup steps falls into this category. Pure-JS libraries like axios, zustand, or date-fns will never trigger this case.

The checking order in one place

When you see this error, walking down the list in order is usually the fastest path to a fix:

  1. Is the package in package.json? → If not, npx expo install
  2. Is Metro's cache fresh? → npx expo start -c
  3. Is the import path spelled correctly? (case, extensions, file vs folder)
  4. If you're using a path alias like @/, is Babel configured for it?
  5. If it's a native module, are you on a Development Build instead of Expo Go?

If you want a single sanity-check to run before each new dev session, npx expo doctor is worth knowing about. It scans your project for common dependency-version mismatches and Expo SDK incompatibilities that tend to surface as resolve errors later. Running it once after a Rork code generation can catch issues before Metro even gets to complain.

Once this order becomes muscle memory, you can keep moving even when Rork generates code with a new dependency. If you want to deepen your Metro debugging skills further, Troubleshooting Metro Bundler and Fast Refresh in Rork and Fixing Runtime Errors in Rork-Generated Code cover the build-time and runtime sides of this same picture.

One last note worth keeping in mind: when you fix one of these issues, restart Metro fully rather than relying on hot reload. Module resolution decisions get cached aggressively, and a fresh start ensures the next failure (if there is one) reflects the current state of your code rather than a half-applied fix.

Next time you hit this error, try npx expo install followed by npx expo start -c first. In my experience that single combination clears 70–80% of cases before you need to dig any further into aliases, builds, or import paths.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-10-15
When EXPO_PUBLIC_ Values Read Fine in Dev but Come Back undefined in Production: Telling the Spellings Apart
The API URL reads fine while developing and turns undefined once the app is built. The cause was how process.env was written. I lined up babel-preset-expo's output for dev and production and tabulated which spellings get replaced and which are left behind.
⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
⬡ Dev Tools2026-09-10
I Added a Dim Screen Button, and iPhones Stayed Dark After the App Was Closed
In expo-brightness, setBrightnessAsync applies only to the current activity on Android, but changes the device brightness itself on iOS. Here is why the cleanup burden falls on one platform only, and a hook that restores brightness through AppState.