Skip to main content

Environment Variables in React (Part 1) #154

📖 Introduction

After learning how to create an optimized Production Build, the next essential step in preparing our React application for different environments (development, staging, production) is managing configuration. Environment variables are a standard way to handle settings that change depending on where your application is running, such as API endpoints, public keys for third-party services, or feature flags. This article, Part 1, introduces what environment variables are, why they're crucial, and how they are typically handled in React projects, especially those bootstrapped with Create React App or Vite.


📚 Prerequisites

Before we begin, you should have:

  • A basic understanding of React application structure.
  • Familiarity with running React apps in development and building them for production.
  • Knowledge of using the command line/terminal.

🎯 Article Outline: What You'll Master

In this article, you will learn:

  • What are Environment Variables? Their purpose and benefits.
  • Why Use Environment Variables? Security, flexibility, and avoiding hardcoding.
  • How Environment Variables Work in React (Bundler Context): Injection at build time.
  • Standard Naming Conventions: REACT_APP_ (for Create React App) and VITE_ (for Vite).
  • Creating and Using .env Files: .env, .env.development, .env.production.
  • Accessing Environment Variables in Your Code.
  • Security Considerations: What NOT to put in client-side environment variables.

🧠 Section 1: What are Environment Variables?

Environment variables are variables whose values are set externally to your application's source code, typically within the environment where your application is running or being built. They provide a way to configure your application without modifying its code, allowing the same codebase to behave differently across various environments like:

  • Development: Your local machine.
  • Testing/CI: Continuous Integration servers.
  • Staging: A pre-production environment that mirrors production.
  • Production: The live environment for your users.

Common Use Cases:

  • API Endpoints: https://api.dev.example.com vs. https://api.prod.example.com.
  • API Keys/Public Keys: Public keys for services like Stripe, Google Maps, Sentry DSNs. (Note: Secret keys should NEVER be exposed client-side).
  • Feature Flags: Enabling or disabling certain features based on the environment (e.g., ENABLE_NEW_DASHBOARD=true).
  • Debug Flags: LOG_LEVEL='debug' in development vs. 'warn' in production.
  • Application Settings: Base URLs, analytics IDs, etc.

Benefits:

  1. Flexibility: Easily change configuration without code changes and redeployments (if variables are set at runtime, though for client-side React, they are typically build-time).
  2. Security: Helps prevent hardcoding sensitive information (like API keys) directly into your version-controlled codebase. .env files containing secrets should not be committed to public repositories.
  3. Collaboration: Different team members can use different local configurations (e.g., pointing to their own local backend).
  4. Consistency: Follows the "Twelve-Factor App" methodology principle of storing config in the environment.

💻 Section 2: How Environment Variables Work in Client-Side React (Build Time Injection)

It's crucial to understand that for most client-side React applications (like those built with Create React App or Vite), environment variables are embedded into your application at build time.

When you run npm run build:

  1. The build tool (Webpack via CRA, Rollup via Vite) reads environment variables from your system or from .env files.
  2. It replaces occurrences of process.env.YOUR_VARIABLE_NAME (or import.meta.env.YOUR_VARIABLE_NAME for Vite) in your JavaScript code with the actual string value of that variable.
  3. This value is then bundled into your static JavaScript files.

This means:

  • Client-Side Accessibility: Once the app is built, these variables are part of the client-side code. Anyone can view them by inspecting your JavaScript bundles in the browser.
  • No Dynamic Updates Post-Build: You cannot change these environment variables for an already built frontend by simply setting a new environment variable on your server after the build is complete. The values are fixed at the time of the build. If you need to change them, you must rebuild your application.
  • Security Implication: Never embed secret keys, private API keys, or any sensitive credentials meant for server-side use into client-side environment variables. They will be exposed to anyone who can access your frontend code.

For server-side rendering (SSR) or frameworks like Next.js (which have both server and client components), the handling of environment variables can be more nuanced, with some variables available only server-side and others passed to the client. But for standard client-side React SPAs, think "build-time injection."


🛠️ Section 3: Standard Naming Conventions and .env Files

Most modern React build tools have conventions for defining and accessing environment variables using .env files.

3.1 - Create React App (CRA) Convention

  • Prefix: Environment variables that you want to be embedded in your client-side bundle must be prefixed with REACT_APP_. Any other variables will be ignored by CRA for security reasons.
    • Example: REACT_APP_API_URL, REACT_APP_GOOGLE_MAPS_KEY.
  • Access in Code: process.env.REACT_APP_YOUR_VARIABLE

3.2 - Vite Convention

  • Prefix: Environment variables that you want to be exposed to your client-side code must be prefixed with VITE_.
    • Example: VITE_API_URL, VITE_STRIPE_PUBLIC_KEY.
  • Access in Code: import.meta.env.VITE_YOUR_VARIABLE
  • Vite also exposes import.meta.env.MODE (e.g., 'development', 'production'), import.meta.env.BASE_URL, import.meta.env.PROD, import.meta.env.DEV.

3.3 - .env Files

You can define environment variables by creating .env files in the root of your project. These files are typically loaded by tools like dotenv (often integrated into CRA and Vite).

Common .env File Hierarchy (Order of Precedence - files on top override files below):

  1. .env.local: Local overrides. This file is intended for local machine-specific settings and should be added to .gitignore. It's not meant for shared team settings.
  2. .env.development.local, .env.test.local, .env.production.local: Environment-specific local overrides. Also should be in .gitignore.
  3. .env.development, .env.test, .env.production: Environment-specific settings. These can be committed to your repository as they might contain default settings for each environment (e.g., pointing dev to a dev API).
  4. .env: Default values. Can be committed.

Example .env file:

# .env (can be committed with general defaults or placeholders)
REACT_APP_API_URL=https://api.example.com
REACT_APP_FEATURE_FLAG_NEW_UI=false

# For Vite projects:
# VITE_API_URL=https://api.example.com
# VITE_FEATURE_FLAG_NEW_UI=false

Example .env.development file:

# .env.development (for development specific settings)
REACT_APP_API_URL=http://localhost:3001/api
REACT_APP_LOG_LEVEL=debug

# For Vite projects:
# VITE_API_URL=http://localhost:3001/api
# VITE_LOG_LEVEL=debug

Example .env.production file:

# .env.production (for production build settings)
REACT_APP_API_URL=https://api.production.example.com
REACT_APP_FEATURE_FLAG_NEW_UI=true

# For Vite projects:
# VITE_API_URL=https://api.production.example.com
# VITE_FEATURE_FLAG_NEW_UI=true

Example .env.local file (SHOULD BE IN .gitignore):

# .env.local (local user overrides, NOT committed)
REACT_APP_API_URL=http://my-custom-local-dev-server:8080/api
# This overrides the API URL for my local machine only.

How they are loaded:

  • When you run npm start (development), variables from .env, .env.development, and then local overrides (.env.local, .env.development.local) are typically loaded.
  • When you run npm run build (production), variables from .env, .env.production, and then local production overrides (.env.local, .env.production.local) are loaded.
  • Variables set directly in your shell environment (e.g., export REACT_APP_API_URL=... before running npm run build) usually take the highest precedence, overriding .env files. This is common in CI/CD pipelines.

🔬 Section 4: Accessing Environment Variables in Your Code

4.1 - In Create React App

You access variables prefixed with REACT_APP_ using process.env:

// src/config.js
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000/api'; // Provide a fallback if needed
const FEATURE_NEW_UI_ENABLED = process.env.REACT_APP_FEATURE_FLAG_NEW_UI === 'true';

export { API_URL, FEATURE_NEW_UI_ENABLED };

// In a component:
// import { API_URL } from './config';
// console.log('Current API URL:', API_URL);

Important:

  • CRA replaces process.env.REACT_APP_... with the actual string value at build time.
  • You cannot destructure process.env like const { REACT_APP_API_URL } = process.env; dynamically in a way that CRA can statically analyze. Access it directly: process.env.REACT_APP_API_URL.

4.2 - In Vite

You access variables prefixed with VITE_ using import.meta.env:

// src/config.js
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api';
const FEATURE_NEW_UI_ENABLED = import.meta.env.VITE_FEATURE_FLAG_NEW_UI === 'true';

// Other built-in Vite env vars:
console.log('Current Mode:', import.meta.env.MODE); // 'development' or 'production'
console.log('Base URL:', import.meta.env.BASE_URL);

export { API_URL, FEATURE_NEW_UI_ENABLED };

// In a component:
// import { API_URL } from './config';
// console.log('Current API URL from Vite:', API_URL);

Vite uses esbuild which performs these replacements.

4.3 - Accessing in public/index.html (CRA)

In Create React App, you can also use environment variables (that have the REACT_APP_ prefix) in your public/index.html file using %VARIABLE_NAME% syntax.

<!-- public/index.html -->
<title>%REACT_APP_APP_TITLE%</title>
<meta name="description" content="%REACT_APP_APP_DESCRIPTION%" />

During the build, CRA will replace these placeholders with the values of the corresponding environment variables. This is useful for things like setting the page title or meta tags. Vite does not support this out-of-the-box for index.html in the same way; for Vite, you'd typically manage such things via JavaScript or plugins.


✨ Section 5: Security Considerations - What NOT to Put Client-Side

This cannot be stressed enough: Because environment variables in client-side React are embedded into the JavaScript bundle at build time, they are publicly accessible.

NEVER store any of the following in client-side environment variables (e.g., .env files processed by CRA/Vite for the frontend):

  • Secret API Keys: Keys for services like AWS, Stripe (secret keys), database credentials, SendGrid, etc.
  • Private Keys or Credentials: Any form of private cryptographic keys, passwords, or sensitive tokens.
  • Proprietary Configuration Data: Internal server addresses or configurations that you don't want publicly known.

What IS safe to put in client-side environment variables?

  • Public API Keys: Keys for services that are designed to be used client-side (e.g., Google Maps JavaScript API key (with appropriate restrictions configured in Google Cloud Console), Sentry DSN, Stripe public key).
  • API Base URLs: The root URL for your backend API.
  • Feature Flags: Boolean flags to enable/disable client-side features.
  • Application Identifiers: Public IDs for analytics services.
  • General Configuration: Non-sensitive settings like default language, theme settings, etc.

Where do secret keys go? Secret keys and sensitive configurations must only reside on your backend server. Your client-side application should make requests to your backend, and your backend then uses its secure environment variables to interact with other services or databases.


💡 Conclusion & Key Takeaways (Part 1)

Environment variables are essential for managing configuration in React applications across different deployment stages. Understanding that they are injected at build time for client-side apps like those made with CRA or Vite is crucial for security and proper usage. Using .env files with appropriate prefixes (REACT_APP_ or VITE_) provides a conventional way to define these variables.

Key Takeaways:

  • Environment variables allow configuration external to your code, varying by environment (dev, prod, etc.).
  • In client-side React (CRA/Vite), they are embedded into the JS bundle at build time.
  • Never embed secret keys or sensitive data in client-side environment variables.
  • Use prefixes like REACT_APP_ (CRA) or VITE_ (Vite) for variables to be exposed to the client.
  • .env files (.env, .env.development, .env.production, .env.local) manage these variables, with .local files being untracked by Git.

➡️ Next Steps

We've covered the basics of defining and accessing environment variables. In "Environment Variables in React (Part 2)", we will explore:

  • More advanced .env file management and loading order.
  • Using environment variables in build scripts or CI/CD pipelines.
  • Type checking environment variables (especially in TypeScript projects).
  • Strategies for handling runtime configuration when build-time variables aren't enough (though this often involves a backend).

Stay tuned for more on configuring your React apps effectively!


glossary

  • Environment Variable: A variable whose value is set outside the application's code, in the environment where it runs or is built.
  • Build Time Injection: The process where environment variable values are embedded directly into the bundled JavaScript code when the application is compiled for production.
  • .env File: A text file used to define environment variables, typically located at the project root.
  • REACT_APP_ (Prefix): The required prefix for environment variables to be accessible in Create React App client-side code via process.env.
  • VITE_ (Prefix): The required prefix for environment variables to be accessible in Vite client-side code via import.meta.env.
  • DSN (Data Source Name): A connection string used by services like Sentry to identify where data should be sent. Often a public key safe for client-side use.
  • Twelve-Factor App: A methodology for building software-as-a-service apps, one principle of which is storing configuration in the environment.

Further Reading