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) andVITE_
(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:
- 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).
- 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. - Collaboration: Different team members can use different local configurations (e.g., pointing to their own local backend).
- 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
:
- The build tool (Webpack via CRA, Rollup via Vite) reads environment variables from your system or from
.env
files. - It replaces occurrences of
process.env.YOUR_VARIABLE_NAME
(orimport.meta.env.YOUR_VARIABLE_NAME
for Vite) in your JavaScript code with the actual string value of that variable. - 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
.
- Example:
- 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
.
- Example:
- 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):
.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..env.development.local
,.env.test.local
,.env.production.local
: Environment-specific local overrides. Also should be in.gitignore
..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)..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 runningnpm 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
likeconst { 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) orVITE_
(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 viaprocess.env
.VITE_
(Prefix): The required prefix for environment variables to be accessible in Vite client-side code viaimport.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
- Create React App: Adding Custom Environment Variables
- Vite: Env Variables and Modes
- The Twelve-Factor App: Config
- dotenv (npm package that powers
.env
loading in many tools)