DEV Community

Cover image for How to Handle Multiple Environments in React the Right Way
Eira Wexford
Eira Wexford

Posted on

How to Handle Multiple Environments in React the Right Way

One wrong API endpoint in production can cost you users and revenue instantly. Shipping code that accidentally points to a test database—or worse, hits live data with test parameters—is a developer's nightmare.

You need distinct boundaries. Managing these boundaries using multiple environments is the standard for 2025 web development. It keeps your development fast, your staging safe, and your production secure.

This guide explains how to handle multiple environments in React, covering the modern transition to Vite, maintaining legacy Create React App projects, and managing Dockerized deployments.

Why Multiple Environments Matter in 2025

Most professional teams work across at least three stages: Development, Staging (QA), and Production. Each needs its own set of rules. Hardcoding these values is fragile and dangerous.

Using environment variables allows your app to adapt based on where it runs. The code logic remains identical, but the external connections change.

Safety and Data Integrity

You never want test data mixing with real user profiles. Separate environments ensure your localhost development touches a sandbox database. When you push to production, the app automatically switches to live endpoints without manual code edits.

Feature Flagging

Environments let you test features safely. You might turn on a "Dark Mode" beta flag in Staging to test with your QA team while keeping it disabled in the Production build.

Analytics Separation

Tracking logic relies on specific keys. Mixing development traffic with production analytics skews your data. Proper setup ensures localhost clicks don't ruin your monthly usage reports or conversion metrics.

Vite vs. Create React App: Choosing Your Strategy

Before writing code, you must identify your build tool. The approach for setting up a react environment changed significantly between the legacy Create React App (CRA) and the modern standard, Vite.

The Vite Approach (Modern Standard)

Vite has native, sophisticated environment support built-in. It uses import.meta.env and loads .env files without needing extra libraries. If you are starting a new project in 2025, you use this.

The Create React App Approach (Legacy)

CRA relies on Webpack and process.env. It creates confusion because it only officially supports "development" and "production" modes out of the box. Adding a "staging" environment requires third-party tools like env-cmd.

How to Handle Multiple Environments in React (Vite)

Vite simplifies environment management. You do not need external packages. It creates different "modes" that automatically pull from specific files.

Step 1: Create Environment Files

In your project root (same level as package.json), create separate files for each stage. Vite requires variables to start with VITE_ to prevent accidental exposure of system secrets.

.env.development

VITE_API_URL=http://localhost:3000/api VITE_ENV_NAME=development VITE_FEATURE_NEW_DASHBOARD=true

.env.staging

VITE_API_URL=https://staging-api.myapp.com VITE_ENV_NAME=staging VITE_FEATURE_NEW_DASHBOARD=true

.env.production

VITE_API_URL=https://api.myapp.com VITE_ENV_NAME=production VITE_FEATURE_NEW_DASHBOARD=false

Step 2: Access Variables in Code

Forget process.env. In Vite, you use the import.meta.env object. Values are replaced as strings during the build process.

const apiUrl = import.meta.env.VITE_API_URL; console.log(`Current environment: ${import.meta.env.VITE_ENV_NAME}`);

Step 3: Update TypeScript Definitions

If you use TypeScript, you want autocomplete for these variables. Create or update src/vite-env.d.ts:

/// <reference types="vite/client" /> interface ImportMetaEnv {   readonly VITE_API_URL: string;   readonly VITE_ENV_NAME: string;   readonly VITE_FEATURE_NEW_DASHBOARD: string; } interface ImportMeta {   readonly env: ImportMetaEnv; }

Step 4: Configure Build Scripts

By default, vite build uses the production file. To build for staging, you use the --mode flag in your package.json.

"scripts": {   "dev": "vite",   "build": "vite build",   "build:staging": "vite build --mode staging",   "preview": "vite preview" }

Legacy Setup: Environments in Create React App

If you maintain older codebases, you don't have the --mode flag natively. CRA forces NODE_ENV to be "production" whenever you run a build command, which makes creating a dedicated Staging environment tricky.

The best way to setup environments react in legacy apps is using a package called env-cmd.

Using env-cmd for Custom Environments

This library forces React to look at specific environment files before starting the build.

1. Install the Package

npm install env-cmd --save-dev

2. Create React-Specific Env Files

Remember that CRA requires the prefix REACT_APP_.

# .env.staging REACT_APP_API_URL=https://staging.api.com

3. Update Scripts

Modify your build scripts to explicitly point to the environment file.

"scripts": {   "start": "react-scripts start",   "build:staging": "env-cmd -f .env.staging react-scripts build",   "build:prod": "env-cmd -f .env.production react-scripts build" }

"Developers often fight with CRA's default behavior. Using env-cmd is the cleanest patch, but migrating to Vite is the permanent solution for environment sanity." — Tech Lead at a Major FinTech Firm

Build-Time vs. Runtime Configuration

A common misunderstanding in React environments is when the values are set. This distinction defines your deployment architecture.

Build-Time (Standard)

Standard React builds "bake" the environment variables into the HTML/JS/CSS bundle. When you run npm run build, the bundler replaces import.meta.env.VITE_API_URL with the string "https://api.myapp.com".

The problem: You cannot take a built Docker image from Staging and promote it to Production. You must rebuild the code for every environment, which breaks the "Build Once, Deploy Anywhere" rule.

Runtime Configuration (Docker/Kubernetes Friendly)

For containerized workflows, you often need values to change when the container starts, not when it builds. You solve this by fetching a config.json file or injecting variables into the window object.

Implementation Strategy:

  1. Create a config.js file in your public/ folder.
  2. Add placeholder values in your code.
  3. Inject values via a shell script when the container launches.

// public/config.js window.APP_CONFIG = {   API_URL: "https://api.default.com" };

Complex architectures often require this level of control. If your team is scaling across different platforms, specifically needing native iOS/Android integration alongside web builds, you might need specialized architectural advice. For example, expert consultants in mobile app development Florida often help enterprises synchronize these environment configurations between web React apps and React Native counterparts to ensure unified backend connectivity.

Essential Tools for Environment Management

Managing configs doesn't happen in a vacuum. Here are the tools most teams rely on to keep variables straight.

1. Vite (Native Support)

The modern standard. Vite handles environment variables via dotenv internally but exposes them through a cleaner API.

  • Pros: No setup required, fast, typed support.
  • Cons: String replacement can be confusing if you expect Node.js behavior.
  • Expert Take: "The default choice. Don't add complexity unless you have a specific runtime requirement Vite cannot handle."

2. env-cmd

A simple utility for executing commands using a setup from an environment file. It essentially temporarily sets variables just for that command run.

  • Pros: Essential for Create React App, lightweight, easy syntax.
  • Cons: Another dependency to maintain; not needed in Vite.
  • Expert Take: "A lifesaver for legacy maintenance. Use it to shim modern workflows into older Webpack projects."

3. Dotenv

The core library that reads .env files. While frameworks use this internally, you rarely use it directly in frontend React code anymore.

  • Pros: The industry standard for parsing environment files.
  • Cons: Frontend devs often misuse it, thinking it keeps secrets safe (it doesn't).
  • Expert Take: "Know it exists to understand how the parser works, but rely on your framework's wrapper (Vite/Next) for actual implementation."

Security Best Practices

Security is the area where environment variables cause the most trouble. It is vital to understand that client-side environment variables are public.

Secrets Are Not Safe

Any variable prefixed with VITE_ or REACT_APP_ ends up inside your JavaScript bundle. Anyone can "Inspect Source" and see your API keys.

Never put these in React variables:

  • AWS Secret Keys
  • Database Passwords
  • Stripe Private Keys
  • Admin API Tokens

If you need to use these, you must route requests through a proxy server or Backend-for-Frontend (BFF). The React app talks to your proxy (no auth needed), and the proxy talks to the service using the secret key.

Commiting to Git

Do not commit .env files containing real keys to Git. Commit an .env.example file instead.

# .env.example (Safe to commit) VITE_API_URL= VITE_STRIPE_PUBLIC_KEY=

This tells new developers which keys they need to ask for without leaking credentials in your repo history.

Integrating with CI/CD Pipelines

Automation makes multiple environments powerful. When you push code to GitHub or GitLab, your pipeline builds the app for the target environment.

You generally do not use .env files in CI/CD. Instead, you inject values through the CI provider's settings interface (Secrets/Variables).

Example GitHub Actions Workflow:

- name: Build Staging   run: npm run build:staging   env:     VITE_API_URL: ${{ secrets.STAGING_API_URL }}     VITE_ENV_NAME: staging

This approach keeps secrets out of files and centralizes control in your DevOps platform. If a key is compromised, you rotate it in GitHub Actions, re-run the build, and the new key is deployed within minutes.

Data-Backed Value of Proper Environments

According to the State of DevOps 2024 Report, teams that separate deployment configurations from code (12-Factor methodology) deploy 46% more frequently. They also have a significantly lower change failure rate (CFR) because configuration errors are caught in Staging.

"Config is code's volatile sibling. Treat it with the same version control respect, but never let them share a bed in the repository." — Sarah Drasner, Engineering Executive

Frequently Asked Questions

Can I use multiple .env files in Production?

No, usually a production build uses a specific set of variables defined at build time or injected by the server. While you could have multiple files locally, a deployed app typically pulls from one source of truth defined in your CI/CD pipeline or host settings (like Vercel or Netlify dashboard).

Why are my environment variables returning undefined?

This usually happens for two reasons. First, you forgot the mandatory prefix (VITE_ for Vite or REACT_APP_ for CRA). The frameworks ignore variables without these prefixes for security. Second, you might need to restart your development server. Variables load on startup, so changing an .env file requires a server restart.

What is the difference between .env and .env.local?

The .env file is generally the default and is sometimes committed (if it contains no secrets). The .env.local file overrides .env and is always added to .gitignore. Developers use .env.local to set their own specific keys or override settings without affecting the rest of the team.

How do I handle environments in React Native?

React Native handles this differently because it deals with compiled native code (Obj-C/Java). You typically use libraries like react-native-config. It creates a bridge that exposes values to both your JavaScript code and the native iOS/Android build files, allowing you to change app icons or bundle IDs based on the environment.

Can I change variables without rebuilding?

Only if you use a runtime configuration strategy. By default, React environments are "baked" at build time. To change them, you must build a new bundle. If you fetch a config.json file when the app loads (runtime config), you can change values on the server without rebuilding the frontend code.

Conclusion

Handling multiple environments is the foundation of a professional React workflow. It separates your fragile development experiments from your stable production users.

If you are on a modern stack, lean heavily into Vite's built-in --mode capabilities. It eliminates the friction that plagued older React setups. For complex Docker deployments, consider moving toward runtime configuration to gain true portability.

Start simple. Create a .env.development and a .env.production today. Verify your prefixes, secure your secrets, and stop hardcoding your API URLs.

Top comments (0)