CORE JSC

International Technology Partnership

React Native

Fixing React Native Environment Variables That Work in Dev but Silently Use Stale Values in Release Builds

The API base URL was updated in .env, the dev build reflects it instantly, and everyone assumes the change is live. Then the release build ships still pointing at the old staging URL — not because the file wasn't updated, but because environment variables in React Native get baked into the JavaScript bundle at build time, and nothing about that process automatically knew a rebuild was needed.

Core JSC Team·September 19, 2026
React NativeEnvironment VariablesRelease BuildsCI/CDDeveloper Tools

The Problem

An environment variable — an API base URL, a feature flag, a third-party key — is updated in .env and confirmed working immediately in the Metro dev server. Everyone assumes the change has taken effect everywhere. Then a release build is produced and shipped, and it's still hitting the old endpoint or behaving with the old flag value, even though git diff clearly shows the .env file was updated and committed before the build ran. Nothing throws an error; the app functions, just against stale configuration that no one intentionally kept.

Why It Happens

React Native environment variables are inlined into the JS bundle at build time, not read at runtime

Unlike a typical Node.js server reading process.env live from the OS environment on every request, libraries like react-native-config or babel-plugin-transform-inline-environment-variables replace references to environment variables with their literal values during the bundling step. Once a bundle exists, changing the .env file has zero effect on that already-built bundle — the values are frozen in at build time, not looked up when the app runs.

Metro's caching can serve an already-bundled JS payload even after .env changes, in dev

The dev server experience of seeing a change "just work" can itself be inconsistent — Metro's fast refresh sometimes picks up an env change because it triggers a full reload, but a cached bundle or a stale Metro process can just as easily keep serving values from before the change, making dev-mode testing an unreliable signal for whether the mechanism itself is actually working correctly.

A release build produced from a stale or cached build artifact never re-reads the current .env at all

CI pipelines and local release builds often cache intermediate build artifacts for speed. If a cached native build or a cached JS bundle from a previous build is reused rather than regenerated, an updated .env file sitting in the repository has no path to actually reach the shipped binary — the stale artifact was never told to look at it again.

Different build variants (dev, staging, production) can each resolve to a different, easily-confused .env file

A project with .env.development, .env.staging, and .env.production depends entirely on the build command correctly selecting the intended file for that specific build variant. Editing the wrong file, or a build script defaulting to the wrong variant when a flag is omitted, produces a build that's internally consistent but pointed at the wrong environment entirely.

The Fix

1. Force a clean bundle and native build for release, not an incremental one, whenever env values change

# Android
cd android && ./gradlew clean && cd ..
npx react-native run-android --variant=release

# iOS
cd ios && xcodebuild clean && cd ..
npx react-native run-ios --configuration Release

A clean build forces the bundler and native toolchain to regenerate every artifact from current source, rather than potentially reusing a cached bundle or intermediate build product that still has the old environment values baked in. This should be standard practice specifically after any .env change, not just when something looks obviously wrong.

2. Print the resolved environment values as part of the build output to catch mismatches before shipping

// A build-time sanity check, e.g. in metro.config.js or a prebuild script
console.log("Building with API_BASE_URL:", process.env.API_BASE_URL);
if (!process.env.API_BASE_URL) {
  throw new Error("API_BASE_URL is not set — aborting build");
}

Logging the actual resolved values at build time — and failing the build loudly if a required one is missing — turns a silent stale-value bug into an immediately visible build-log line, catching the mismatch before the artifact ships rather than after a user reports unexpected behavior.

3. Make the build variant and its corresponding env file explicit in the build command, never implicit

# Explicit, unambiguous:
ENVFILE=.env.production npx react-native run-android --variant=release

# Rather than relying on a script that silently defaults to one file
# when no flag is passed

Requiring the environment file to be named explicitly in every build invocation — with no default fallback that could silently select the wrong one — removes the possibility of a build variant resolving to an unintended .env file simply because a flag was forgotten.

4. Verify the shipped bundle's actual embedded values, not just the source .env file, before release

# Extract and grep the bundled JS for the expected value
npx react-native bundle --platform android --dev false \
  --entry-file index.js --bundle-output /tmp/release.bundle
grep -o "https://api[^\"]*" /tmp/release.bundle | sort -u

Checking the actual compiled bundle for the expected value — rather than trusting that the source .env file being correct guarantees the build picked it up — catches exactly the class of bug where the file was updated correctly but the build process didn't actually incorporate the change.

Why This Works

Each fix targets a different point where a build can silently diverge from the current source configuration. Clean builds remove the possibility of a cached artifact carrying old values forward; build-time logging surfaces the resolved values where they can actually be checked before shipping; explicit env-file selection removes ambiguity about which variant's configuration a given build actually used; and verifying the compiled bundle directly confirms the change genuinely reached the shipped artifact, rather than assuming it did because the source file looked correct.

Conclusion

A React Native release build using stale environment values isn't usually a typo in .env — it's a gap between when the file was edited and when a build actually re-read and re-baked it into a fresh bundle. Force clean builds after any environment change, log resolved values at build time so a missing or wrong value fails loudly instead of shipping silently, make env-file selection explicit in every build command, and verify the compiled bundle's actual embedded values directly before trusting that a release is using current configuration.