◉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-04-24Intermediate

Diagnosing Metro Bundler Freezes and Broken Fast Refresh in Rork Apps

When your Rork-generated Expo app stops picking up code changes or Metro freezes mid-bundle, here is the diagnostic order and the safe reset steps that actually work.

Metro4Fast RefreshExpo219React Native239Troubleshooting43

You save a file. Nothing happens. The terminal looks alive, but your app is still showing the state from five minutes ago. If you have spent any real time with a Rork-generated Expo project, you know this feeling. What makes it frustrating is that the cause is almost never the same twice. Sometimes it is a stale cache, sometimes it is node_modules, sometimes it is Watchman hitting its file-watch ceiling.

Metro bundler and Fast Refresh issues are part of the React Native landscape. This article walks through the symptoms I keep running into, and the order in which to check things so you can get back to work in minutes rather than hours. I want to go beyond "clear the cache and pray" and actually explain why each step matters.

The Three Signals to Check First

When a Fast Refresh issue hits, there is a strong urge to immediately run rm -rf node_modules. Resist it for 30 seconds. Check these three things first, and you will usually know where the problem lives.

First, look at the Metro terminal output. Are you seeing BUNDLE or Hot update messages when you save a file? If yes, Metro itself is running — the problem is on the device side (the simulator or phone is not receiving updates). If no, Metro itself is stuck.

Second, check the path of the file you edited. Is it inside your project root? Did you accidentally modify a file inside node_modules? Is it a symlinked file? Metro only watches specific paths, and edits outside those paths will never trigger Fast Refresh. This one catches people more often than you would think.

Third, look for warnings. If Fast Refresh tried to run but gave up, you will often see a message like "Fast Refresh had to perform a full reload" either in the terminal or in your editor's console. That message points to a structural issue in your component code (more on this below).

The Five Reasons Metro Actually Freezes

In my experience, Metro freezes almost always trace back to one of these five root causes.

Corrupted or Bloated Cache

Metro stores intermediate artifacts in .expo/ and /tmp/metro-*. When these get corrupted, you get stale bundles even after saving your code. This happens especially often when you edit Rork-generated SwiftUI code directly in Xcode — external file changes confuse Metro's change detection.

Watchman File-Watch Limits

On macOS, Watchman monitors file changes, but the default per-process limit is 8,192 files. Monorepos or projects with heavy dependency graphs blow past this easily, and any changes beyond that limit are silently ignored.

Mismatch Between node_modules and Lockfile

Mixing npm install and yarn install, or reinstalling dependencies under a different Node.js version, leaves your node_modules out of sync with your lockfile. Metro tries to carry on, but certain module resolutions hang indefinitely.

Expo SDK Version Drift

When the Expo SDK version in app.json no longer matches the expo package version in package.json, Metro starts fine but hangs partway through bundling. This is common right after you update your app in Rork but leave the local copy on the old version.

Port Conflicts

Metro defaults to port 8081. If another process is holding it, Metro either moves to a different port or fails to start. The device-side app is still pointing at 8081, so you get a "Metro is running but won't connect" situation.

Diagnostic Flow by Symptom

Symptom A: Saves do not update the screen

Check the Metro log. If BUNDLE messages appear, the issue is on the device side. Manually reload the app (⌘R in the iOS simulator, shake the device or open the menu on Android). If a manual reload shows the latest code, then only Fast Refresh is broken — suspect your component structure.

If there is no BUNDLE message, Metro cannot detect your file changes. Restart Watchman first, it is the fastest fix.

# Reset Watchman state
watchman watch-del-all
watchman shutdown-server
 
# Restart Metro with a clean slate
npx expo start --clear

Expected output: after Starting Metro Bundler, you should see BUNDLE ./App.tsx. If it stalls here, move to the next step.

Symptom B: Metro hangs on "Loading dependency graph..."

Metro is stuck resolving dependencies. This is almost always a node_modules inconsistency. Clean up in a safe order:

# Step 1: Kill Metro and the simulator
# Ctrl+C to stop Metro, then quit the simulator
 
# Step 2: Remove caches and dependencies
rm -rf node_modules
rm -rf .expo
rm -f package-lock.json
 
# Step 3: Verify npm cache just in case
npm cache verify
 
# Step 4: Reinstall
npm install
 
# Step 5: Start with a cache clear
npx expo start --clear

Expected output: a QR code with Metro waiting on exp://192.168.x.x:8081.

Symptom C: "Fast Refresh had to perform a full reload" keeps appearing

This is a code-structure problem, not a tooling problem. Fast Refresh only handles "pure updates" in React function components. These patterns force a full reload:

  • Running side-effects at the top level of a file (like a bare console.log)
  • Multiple export default statements
  • Mixing class and function components in the same file
  • Anonymous functions as the default export (export default () => {})

The anonymous default export is especially sneaky. Rork sometimes generates them, and simply naming the function often fixes Fast Refresh.

// ❌ Breaks Fast Refresh
export default () => {
  return <View>...</View>
}
 
// ✅ Named function keeps Fast Refresh stable
export default function HomeScreen() {
  return <View>...</View>
}

The Full Reset Playbook (In Safe Order)

When nothing else works, here is the escalation ladder. Run each level, check if the issue is resolved, and only move on if it is not. You rarely need to go all the way.

#!/bin/bash
# rork-metro-reset.sh — escalate in safe order
 
# Level 1: Metro cache only (lightest)
npx expo start --clear
 
# Level 2: Reset Watchman too
watchman watch-del-all && watchman shutdown-server
npx expo start --clear
 
# Level 3: Blow away .expo and node_modules
rm -rf .expo node_modules
npm install
npx expo start --clear
 
# Level 4: Delete the lockfile to fully re-resolve
rm -rf .expo node_modules package-lock.json
npm install
npx expo start --clear
 
# Level 5: In the iOS simulator: Erase All Content and Settings
# Level 6: Delete Xcode DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData/*

Levels 3 and beyond mean long reinstalls, so you really want Levels 1 or 2 to do the job. In my own setup, Level 2 resolves about 80% of the issues I run into.

Preventing Recurrence with Better Project Hygiene

If the same symptom keeps coming back every week, the project setup itself is worth revisiting.

Make sure .expo/ is in .gitignore. On a team, one person accidentally committing their cache directory can poison every other developer's Metro instance.

Pin your Node.js version. Drop an .nvmrc at the project root so everyone installs against the same Node.js build. Subtle node_modules differences from version drift are a surprisingly common Metro-stall trigger.

# .nvmrc
20.11.0

Raise the Watchman limits on large projects. You can increase the number of watchable files on macOS with:

echo "kern.maxfiles=524288" | sudo tee -a /etc/sysctl.conf
echo "kern.maxfilesperproc=524288" | sudo tee -a /etc/sysctl.conf

A restart applies the changes. For heavy Expo apps or projects leaning on react-native-reanimated and other file-heavy libraries, this headroom pays off.

If you are hitting issues outside the Metro layer, the Expo dependency error fix guide and the complete build error guide cover the adjacent ground.

One Concrete Next Step

Metro problems are annoying precisely because they are hard to reproduce. To future-proof yourself, consider adding scripts/reset.sh to your project today, containing Levels 1 through 3 from above. When the next stall hits, you will be back at your editor in under a minute instead of breaking flow to recall the exact commands. Protecting momentum matters as much as the raw speedup. In my own workflow, this small addition has meaningfully reduced Metro-related stress.

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.