Intro
Expo accelerates React Native development, but its abstraction layer can turn a small misconfiguration into a confusing stack trace. This guide helps developers, DevOps consultants, and technical startup teams move from an observed problem to a verified result. You will learn how to identify the installed version, understand the deployment topology, check prerequisites, and inspect the exact component causing trouble.
The approach throughout is operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify the result, and document how to recover if the expected state is not reached. Every fix includes the command or signal that confirms success, so you can apply it with confidence whether you work locally, in CI/CD, or on a staging build.
Version and Environment Inventory
Many Expo errors disappear once you align the SDK, CLI, and native runtime versions. Before touching any code, build a clear inventory of the environment.
Start with read-only observations. These commands do not modify anything and give you the current state:
# Check the Expo CLI version
expo --version
# Expected output example: 6.3.10
# Check the installed Expo SDK in package.json
node -e "console.log(require('./package.json').dependencies.expo)"
# Expected output example: ~51.0.28
# List globally installed Expo-related packages
npm ls -g --depth=0 | grep expo
# Expected output example: [email protected]
# Check the current Node.js version
expo diagnostics
# Expected output includes: Node.js version, npm version, Expo CLI version, and project SDK version
Record the output and timestamp it. If you are on a team, store these details in a shared troubleshooting log. The goal is to capture the baseline before any change.
Prerequisites for running these commands:
- Node.js 18 LTS or newer for Expo SDK 49 and above
- npm 9+ or yarn 1.22+ installed
- The project directory accessible from the terminal
- For EAS Build debugging, a valid Expo account and
eas-cliinstalled globally (npm install -g eas-cli)
Once you have the baseline, define the expected result before any intervention. For example, if the goal is to upgrade from SDK 48 to 51, the expected output after upgrade is expo --version showing the new SDK and npx expo start launching without dependency errors.
The smallest justified change might be updating just the expo package, not the entire monorepo. Always run npx expo install --fix after changing the SDK version. This command aligns all React Native and Expo dependencies to compatible versions:
npx expo install --fix
# Expected output: a list of packages updated to compatible versions, e.g.,
# "The following packages were updated:
# [email protected]
# [email protected]"
If the upgrade fails midway, the recovery path is:
git checkout package.json package-lock.json
# Restores the previous working dependency set
npx expo start --clear
# Clears Metro bundler cache and restarts
Never paste real credentials, tokens, or private keys into any command. Use environment variables or placeholder files with clear names like EXPO_TOKEN=<your-token> in examples.
Safe Configuration Path
The most common source of Expo errors is a configuration file that contains a value unsupported by the current SDK. This section covers app.json/app.config.js, eas.json, and metro.config.js.
First, observe the project configuration without changing anything:
npx expo config --type public
# Expected output: the resolved public config, including plugins, versions, and EAS project ID
If you need to inspect a specific field, use --json and pipe to jq:
npx expo config --type public --json | jq '.expo.version'
# Expected output example: "1.0.0"
When you see an error like Plugin "expo-splash-screen" is not compatible with SDK 51, the fix is often a version bump in app.json plugins, not a code change. Here is a safe sequence:
- Check the current plugin version declared:
node -e "console.log(require('./app.json').expo.plugins)"
# Expected output example: [ "expo-splash-screen" ]
- Verify the compatible version from the Expo docs or the plugin package:
npm view expo-splash-screen@latest version
# Expected output example: 0.27.5
- Update only that plugin in
app.config.jswith an explicit version. Here is a minimal example using JavaScript config with a placeholder:
// app.config.js
module.exports = {
expo: {
name: "MyApp",
slug: "my-app",
version: "1.0.0",
plugins: [
["expo-splash-screen", { backgroundColor: "#ffffff" }]
],
},
};
If you need to pin the plugin package version, run:
npx expo install [email protected]
- Verify the configuration resolves without errors:
npx expo config --type public
# Expected output: no error, config includes the plugin with the correct version
Recovery if the config becomes invalid: reset only the changed file from version control, or if using app.config.js, revert the edit and run npx expo start --clear.
Avoid placing secrets directly in app.json. Instead, use environment variables in app.config.js with process.env.API_URL and never hardcode production identifiers.
Verification and Diagnostics
When an Expo app fails to start, crashes at runtime, or shows a red screen, you need a systematic diagnostic approach. This section covers log capture, Metro bundler status, and device-specific checks.
Start with the Expo CLI's built-in diagnostics:
npx expo-doctor
# Expected output: a report of potential issues with the project configuration and dependencies,
# e.g., "Found 2 issues: package.json has invalid dependency range for expo-updates, app.json is missing ios.bundleIdentifier"
If expo-doctor reports no issues but the app still fails, capture Metro bundler logs with verbose output:
npx expo start --verbose
# Expected output: detailed bundling logs, including file transformations and any module resolution errors
For runtime errors on a device or emulator, use the Expo Go app or a development build, and then open the device logs:
npx expo start
# Then press 'j' to open the debugger in Chrome, or 'm' to toggle the developer menu on Android
To read system logs from an Android device:
adb logcat | grep ReactNativeJS
# Expected output: JavaScript console logs and errors from the running app
For iOS simulator:
xcrun simctl spawn booted log stream --predicate 'processImagePath endswith "MyApp"'
# Expected output: continuous log stream for the app process
One common diagnostic is checking that all native modules are linked. Run this command to see the list of installed native modules:
npx expo-modules-autolinking verify
# Expected output: a table of installed modules and their autolinking status
If a module is missing, the error often says Cannot find native module 'ExpoCamera'. To verify the autolinking configuration:
npx expo-modules-autolinking search expo-camera
# Expected output: path to the module and its version, e.g.,
# "expo-camera found at node_modules/expo-camera (version 14.0.6)"
For network-related errors, test connectivity to Expo services:
curl -I https://exp.host
# Expected output: HTTP/2 200 or 301, indicating the service is reachable
Record all command outputs with timestamps. If you are debugging with a team, paste the relevant lines, not the entire log, to keep the thread focused.
Failure Modes and Recovery
Different failure modes require different recovery strategies. This section covers the most frequent Expo-specific failures: bundling errors, dependency conflicts, EAS Build failures, and OTA update issues.
Bundling errors often appear as Unable to resolve module ./src/screens/Home or SyntaxError: Unexpected token. To recover, first clear the Metro cache and restart:
npx expo start --clear
# Expected output: Metro bundler restarts with a clean cache
If the error persists, verify the file path exists and matches the import exactly, including case sensitivity. On Linux and macOS, paths are case-sensitive; on Windows, they are not, which can cause CI/CD mismatches.
Dependency conflicts show up as Invariant Violation: requireNativeComponent: "RNCSafeAreaProvider" was not found in the UIManager. This usually means a native module is not linked or has a version mismatch. Reinstall dependencies with a clean slate:
rm -rf node_modules
npm install
npx expo install --fix
# Expected output: clean install with corrected dependency versions
For EAS Build failures, first check the build logs online with:
eas build:list
# Expected output: a table of recent builds with status, e.g., "errored", "finished", "in-progress"
To view a specific build's logs:
eas build:view --platform android --json | jq '.logs'
# Expected output: full build log output, searchable for error lines
Common EAS Build errors include missing google-services.json for Android push notifications or an invalid bundleIdentifier in app.json. The recovery is to add the file to the project root or correct the identifier, then run:
eas build --platform android --profile preview --non-interactive
# Expected output: a new build starts and eventually returns a build URL
OTA update failures occur when expo-updates is misconfigured. Check your eas.json update configuration:
eas update:list
# Expected output: a list of published updates with timestamps and runtime versions
If an update fails to apply, verify the runtime version in app.json matches the binary's runtime. Use expo-updates diagnostics:
npx expo-updates diagnostics
# Expected output: information about the updates configuration and last check result
Recovery may require rolling back to a previous update:
eas update:rollback --channel production
# Expected output: confirmation that the rollback was initiated
Always define a recovery verification before you need it. For example: "After rollback, the app should load version 1.0.1, confirmed by checking the expo-updates debug screen in the development build."
Operations Checklist
Use this checklist for any Expo troubleshooting session. Each item includes a concrete example to make it actionable.
- [ ] Capture environment baseline: Run
npx expo diagnosticsand save output with timestamp, e.g.,2025-04-01_10-00_expo-diagnostics.txt. - [ ] Check dependency health: Run
npx expo-doctor. Expected output: no issues or a list of actionable items. - [ ] Reproduce the error with minimal steps: Document the exact navigation path, e.g., "Open app > Login > Press 'Submit' > Red screen with 'Cannot read property map of undefined'."
- [ ] Isolate the change: Use
git diffbefore and after. If working on a new feature, stash unrelated changes withgit stash. - [ ] Apply the smallest fix: For example, if the error is a missing icon, run
npx expo install @expo/vector-iconsand update the import. - [ ] Verify with a read-only command: After adding an icon, run
npx expo export --platform web --output-dir distand check the dist folder for the icon asset. - [ ] Test recovery path: Before applying a risky change, try the rollback command in a dry run if available, e.g.,
git checkout -- package.jsonthennpm installandnpx expo start. - [ ] Document the outcome: In your team's runbook, note the error, the fix, the verification command, and the rollback procedure.
- [ ] Secure secrets: Never log
process.env.EXPO_TOKEN. Useexpo logininteractively or store the token in a CI/CD secret manager. - [ ] Review related dependencies: Check React Native, Android, and GitLab CI/CD only if they are part of the error chain. For example, if a CI build fails on Android, ensure the Docker image has the correct Android SDK version.
A sample completed row for a dependency conflict:
| Step | Example |
|---|---|
| Baseline | npx expo diagnostics shows SDK 50, Node 20 |
| Error | RNCSafeAreaProvider not found |
| Fix | rm -rf node_modules && npm install && npx expo install --fix |
| Verification | npx expo start --clear, app loads without red screen |
| Recovery | git checkout package-lock.json && npm install |
Conclusion
Expo common errors become manageable when you treat them as version-scoped, observable, and reversible events. Copying a command without checking prerequisites and expected output is not an operations procedure; it is a guess. Instead, choose one low-risk verification for your current problem, record the current state, run the documented check, compare the result with the expected signal, and review only the dependencies that affect this error, such as React Native, Android, or GitLab CI/CD.
A reliable technical workflow makes failure visible, protects sensitive values, limits changes to the intended resource, and defines recovery verification before an incident forces the decision. With the concrete examples in this guide, you can move from a stack trace to a stable build in a predictable, reviewable way.