Local Development Basics
10 examples to get you started with Local Development - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Local Development - 7 basic and 3 intermediate.
Local development with Expo is a Metro dev server on your laptop plus a client app on a simulator, emulator, or physical device. Scaffold once, then reuse the same start command every day.
npx create-expo-app@latest MyDevApp --template blank-typescript
cd MyDevAppConfirm the SDK pin before your first run:
{
"dependencies": {
"expo": "~57.0.4",
"react": "19.2.3",
"react-native": "0.86.0"
}
}Install Expo Go on a physical device (App Store / Play Store) if you plan to scan QR codes. Simulators and emulators do not need Expo Go - the CLI opens them directly.
Tooling: These examples target Expo SDK 57 (
expo~57.0.4), React Native 0.86.0, and React 19.2.3. Metro serves JavaScript on port 8081 by default.
npx expo start is the daily entry point - it boots Metro, prints connection URLs, and shows a QR code in the terminal.
cd MyDevApp
npx expo start› Metro waiting on exp://192.168.1.42:8081
› Scan the QR code above with Expo Go (Android) or the Camera app (iOS)
› Press i │ open iOS simulator
› Press a │ open Android emulator
› Press r │ reload app
› Press ? │ show all commandsexp:// URL is what simulators, Expo Go, and dev clients use to download the JS bundlegit, tests, or eas commandsnpx expo doctor to catch SDK or Node version mismatches before debugging network issuesRelated: Expo Platform Basics - project layout and SDK pin | Hot Reload vs Fast Refresh - what survives a save
LAN advertises your laptop's local IP so phones and tablets on the same Wi‑Fi can reach Metro - no extra flags required.
# LAN is the default - Metro advertises your machine's local IP
npx expo start# Sanity-check that the device can reach your machine (replace with the IP Metro printed)
curl -I http://192.168.1.42:8081/statusRelated: Physical Device Testing - USB debugging and wireless pairing when LAN is unreliable
Tunnel routes Metro through Expo's relay so a device can load your bundle even when LAN is blocked - at the cost of speed.
# Use when LAN QR scans fail on guest Wi‑Fi, VPN, or remote pairing
npx expo start --tunnel› Metro waiting on exp://u3k1-xxxxx.exp.direct:80
› Tunnel ready.npx expo login so Expo can provision the relay URLnpx expo start --tunnel --clearRelated: Physical Device Testing - when USB or internal builds replace tunnel entirely
Scanning the terminal QR code is the fastest way to open a project on a real device without Xcode or Android Studio.
npx expo start
# Point your phone at the ASCII QR block in the terminal| Platform | How to scan | Client |
|---|---|---|
| iOS | Camera app → tap the exp:// banner | Opens Expo Go (install from App Store if prompted) |
| Android | Expo Go → Scan QR code | Must use Expo Go's built-in scanner - the stock camera app is unreliable |
exp:// URL Metro prints - scanning is just a shortcut to typing the URL manuallyRelated: expo-dev-client - when Expo Go is no longer sufficient | iOS Simulator & Android Emulator - simulator-first workflows
With Metro running, single-key terminal shortcuts boot simulators without memorizing platform-specific commands.
npx expo start| Key | Action |
|---|---|
i | Open the default iOS Simulator and load the bundle |
Shift + i | Pick a different iOS simulator (screen size, OS version) |
a | Open the default Android emulator (requires a created AVD) |
Shift + a | Pick a different Android emulator |
r | Reload the JavaScript bundle |
m | Toggle the in-app developer menu on the connected device |
j | Open React Native DevTools (debugger attached to the running app) |
expo start is focused - they are not global hotkeysi / a compile nothing themselves; they launch the simulator and deep-link into your running Metro servera reports no emulator, create an AVD in Android Studio → Device Manager, then press a againj opens the RN 0.86 debugger story - element inspector, performance, and component tree without FlipperRelated: iOS Simulator & Android Emulator - AVD setup, snapshots, and device matrix | React Native DevTools - inspector and performance panels
Stale bundles after dependency or config changes are fixed by restarting Metro with a clean cache.
# After npm install, SDK bump, or babel.config.js edits
npx expo start --clear
# Combine with a host mode when needed
npx expo start --clear --tunnel# Nuclear option when --clear is not enough (macOS / Linux)
rm -rf node_modules/.cache .expo
npx expo start --clear--clear wipes Metro's transform cache - the first reload after is slower, then speed returns to normal--clear after npx expo install --fix, adding a Reanimated Babel plugin, or upgrading expo in package.json.expo resets local CLI state (cached tunnel credentials, last opened device) - safe, but you may need to rescan QRRelated: Hot Reload vs Fast Refresh - diagnosing stale bundles vs state loss | Expo CLI Best Practices - daily hygiene checklist
Encode host mode and platform entry points in package.json so every developer runs identical commands.
{
"scripts": {
"start": "expo start",
"start:clear": "expo start --clear",
"start:tunnel": "expo start --tunnel",
"ios": "expo start --ios",
"android": "expo start --android",
"ios:device": "expo run:ios --device"
}
}npm run start # LAN + QR - daily default
npm run start:tunnel # Pairing session for QA on another network
npm run ios # Simulator shortcut without an interactive terminalexpo start --ios and --android combine Metro boot and client launch - useful in CI smoke scripts and VS Code tasksexpo run:ios --device compiles and installs on a plugged-in iPhone (requires Apple development signing configured)package.json instead of documenting one-off flags in README - onboarding stays copy-pasteablestart:clear with a post-install hook or document "run after every git pull that touches package-lock.json"Related: Expo CLI Best Practices - naming conventions and shared troubleshooting runbooks
expo-dev-client)Once you add native modules outside the Expo Go runtime, start Metro in dev-client mode so QR codes and URLs open your custom build instead of Expo Go.
# Install the dev-client library (once per project)
npx expo install expo-dev-client
# Build and install the native shell locally (first time, or after native changes)
npx expo run:ios
# npx expo run:android
# Daily JS iteration - same Metro server, different client
npx expo start --dev-client{
"scripts": {
"start": "expo start --dev-client",
"ios": "expo start --dev-client --ios",
"android": "expo start --dev-client --android"
}
}--dev-client changes the QR payload and deep link so installed development builds connect automaticallyapp.json plugins, new native modules, SDK upgrades) - JS-only edits do not require rebuilds in the interactive terminal to switch between Expo Go and development build when both clients are installedRelated: expo-dev-client - launcher settings and native module iteration | Expo Go vs Development Builds - choosing a client model
Simulators on the same machine can use localhost; physical devices always need a reachable LAN or tunnel address.
# Simulators only - Metro listens on 127.0.0.1
npx expo start --localhost
# Physical device on desk - LAN is the default (no extra flag)
npx expo start
# Force a specific IP when Metro picks the wrong interface (VPN, Docker, Thunderbolt bridge)
REACT_NATIVE_PACKAGER_HOSTNAME=192.168.1.42 npx expo start| Host | Best for | Device requirement |
|---|---|---|
localhost | iOS Simulator, Android emulator on same laptop | Client must share the machine |
lan | Physical phone/tablet on same Wi‑Fi | Must resolve laptop's RFC1918 IP |
tunnel | Remote stakeholder device, isolated guest Wi‑Fi | Internet access on both ends |
--localhost avoids advertising IPs on untrusted networks - useful on coffee-shop Wi‑Fi when you only need the simulatorREACT_NATIVE_PACKAGER_HOSTNAME overrides auto-detection when your Mac has multiple active interfaces and Metro prints the wrong oneadb reverse when using localhost-heavy setups - prefer --host lan for mixed simulator + hardware sessionsREADME - "always tunnel" and "always LAN" groups drift quickly without scripted defaultsRelated: Physical Device Testing - wireless debugging when hostname overrides are not enough
Combine client type, host mode, cache policy, and debugger entry into one repeatable loop for SDK 57 projects.
#!/usr/bin/env bash
# scripts/dev.sh - optional team helper
set -euo pipefail
CLIENT="${1:-dev}" # dev | go
HOST="${2:-lan}" # lan | tunnel | localhost
FLAGS=()
case "${HOST}" in
tunnel) FLAGS+=(--tunnel) ;;
localhost) FLAGS+=(--localhost) ;;
lan) ;; # default - no extra flag
*) echo "Unknown host: ${HOST}" >&2; exit 1 ;;
esac
if [[ "${CLIENT}" == "dev" ]]; then
FLAGS+=(--dev-client)
elif [[ "${CLIENT}" == "go" ]]; then
FLAGS+=(--go)
fi
# Clear cache on Mondays or after dependency changes
if [[ "${CLEAR_CACHE:-}" == "1" ]]; then
FLAGS+=(--clear)
fi
npx expo start "${FLAGS[@]}"# Expo Go on office LAN
./scripts/dev.sh go lan
# Custom dev client through tunnel for a remote QA device
./scripts/dev.sh dev tunnel
# Simulator-only Monday restart with a clean cache
CLEAR_CACHE=1 ./scripts/dev.sh dev localhostTroubleshooting order when the app will not connect:
npx expo start --clear after any npm install.REACT_NATIVE_PACKAGER_HOSTNAME).--dev-client after expo run:*.j to attach React Native DevTools if the app loads but behaves incorrectly.CLEAR_CACHE=1 as an opt-in env var beats always passing --clear, which punishes every start unnecessarilygo and dev paths explicit - auto-detecting the installed client fails silently when both apps are on the devicei) to separate network issues from JavaScript defectsRelated: React Native DevTools - attach debugger after the bundle loads | Flipper & Alternative Debuggers - native-level inspection when JS tools are not enough | Expo CLI Best Practices - team conventions and CI handoff
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026