How Environment Variables Work in Create React App: Build-Time Injection Explained
Create React App injects environment variables at build time by loading .env files, filtering for the REACT_APP_ prefix, and embedding the values into your JavaScript bundle using webpack's DefinePlugin, making them accessible via process.env in your React code.
Create React App (CRA) from the facebook/create-react-app repository provides a zero-configuration system for managing environment variables in Create React App through a secure, compile-time injection pipeline. Understanding how these variables flow from your .env files into the running application requires examining the build process orchestrated by the react-scripts package.
The Environment Variable Pipeline in react-scripts
The core logic resides in packages/react-scripts/config/env.js, which executes during the build to transform your environment files into injectable code. This module handles file discovery, variable filtering, and webpack configuration generation in a strict sequence.
Loading and Parsing .env* Files
CRA automatically discovers and loads environment files based on your current NODE_ENV. According to the source code in env.js (lines 25‑34), the framework looks for files in this specific order:
.env.env.local.env.developmentor.env.production(depending on the current environment).env.development.localor.env.production.local
Each existing file is parsed using dotenv and dotenv-expand (lines 36‑48) to resolve variable references and expand values before injection.
Normalizing NODE_PATH
Before processing variables, CRA resolves any relative paths defined in NODE_PATH to ensure consistent module resolution across monorepos (lines 51‑65). This normalization step guarantees that custom import paths work correctly regardless of where the build command is executed.
The REACT_APP_ Prefix Filter
Not all environment variables reach your React code. The env.js module enforces a strict naming convention using the RegExp /^REACT_APP_/i (line 69) to filter variables. Only names matching this pattern (case-insensitive) are retained for the client bundle.
NODE_ENV is automatically included in the final environment object regardless of prefix, providing access to 'development', 'production', or 'test' values within your application logic.
Build-Time Injection via getClientEnvironment
The getClientEnvironment(publicUrl) function (line 71) constructs the final environment object consumed by webpack. This function returns two critical structures:
raw(lines 72‑83): Contains the plain string values that will be accessible in your application codestringified(lines 101‑107): Contains JSON-stringified versions of each value for safe injection into webpack's DefinePlugin
When webpack processes your code, the DefinePlugin replaces all occurrences of process.env.REACT_APP_VARIABLE_NAME with the actual string value at compile time. This means the final bundle contains hardcoded strings rather than runtime environment lookups.
Using Environment Variables in Your Application
Once configured in your .env files, variables become accessible throughout your React components and utility modules.
Accessing Variables in JavaScript
Define your variables in the project root:
# .env
REACT_APP_API_URL=https://api.example.com
REACT_APP_ENABLE_ANALYTICS=true
Access them using the global process.env object:
// src/config/api.js
const apiUrl = process.env.REACT_APP_API_URL;
const analyticsEnabled = process.env.REACT_APP_ENABLE_ANALYTICS === 'true';
export { apiUrl, analyticsEnabled };
Injecting Variables into HTML
Starting with [email protected], you can reference environment variables directly in public/index.html using the %VARIABLE_NAME% syntax:
<!-- public/index.html -->
<title>%REACT_APP_SITE_TITLE%</title>
<meta name="description" content="%REACT_APP_SITE_DESCRIPTION%">
During the build process, CRA replaces these placeholders with the corresponding environment values, allowing dynamic meta tags and titles without ejecting.
Environment-Specific Configuration
CRA supports cascading configuration files that allow different settings for development and production environments. When you run npm start, CRA loads .env.development; when you run npm run build, it loads .env.production.
Create environment-specific overrides:
# .env.production
REACT_APP_API_URL=https://api.prod.example.com
REACT_APP_DEBUG_MODE=false
Local overrides using the .local suffix take precedence and should be added to .gitignore to prevent committing sensitive machine-specific values:
# .env.local
REACT_APP_LOCAL_SECRET=development-key-only
Temporary Shell Variables
For one-off testing without modifying files, you can prefix the npm command on Unix systems:
REACT_APP_TEMP_TOKEN=abc123 npm start
Security and Performance Considerations
Build-time embedding means environment variables become literal strings in your static JavaScript files. This architecture provides two critical constraints:
- No runtime changes: You cannot modify environment variables after building the application without triggering a new build
- No secrets in client code: Because values are visible in the bundled JavaScript served to browsers, CRA documentation explicitly warns against storing API keys, database credentials, or other secrets in
REACT_APP_variables
The webpack DefinePlugin configuration generated by env.js looks conceptually like this:
// Simplified representation of the internal webpack configuration
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"',
PUBLIC_URL: '"/"',
REACT_APP_API_URL: '"https://api.example.com"',
REACT_APP_ENABLE_ANALYTICS: '"true"'
}
});
Summary
- Environment variables in Create React App are processed at build time by
packages/react-scripts/config/env.js, not at runtime - Only variables prefixed with
REACT_APP_are exposed to the client bundle, alongside the automaticNODE_ENVandPUBLIC_URL - The system loads
.envfiles in a specific precedence order, with.localvariants overriding base files - Values are embedded via webpack's DefinePlugin, transforming
process.env.VAR_NAMEreferences into static strings in the final bundle - Variables can be injected into
public/index.htmlusing the%REACT_APP_VAR%placeholder syntax - Never store secrets in
REACT_APP_variables since they become visible in the compiled static assets
Frequently Asked Questions
Why do my environment variables need the REACT_APP_ prefix?
Create React App intentionally filters environment variables using the RegExp /^REACT_APP_/i (as implemented in env.js line 69) to prevent accidental exposure of system-level variables to the client-side bundle. This security measure ensures that only explicitly intended variables become part of your public JavaScript code, while server-side or shell variables remain isolated from the browser.
Can I change environment variables after building the app?
No. Because CRA embeds environment variables at build time using webpack's DefinePlugin, the values become hardcoded strings in your static JavaScript files. To change a variable's value, you must rebuild the application. This differs from server-side Node.js applications where process.env is evaluated at runtime.
How do I use environment variables in public/index.html?
Starting with [email protected], you can reference any REACT_APP_ variable in public/index.html using the percent-sign syntax: %REACT_APP_VARIABLE_NAME%. During the build process, CRA replaces these placeholders with the actual values from your environment files, enabling dynamic titles, meta tags, and CDN URLs without ejecting from the default configuration.
What is the order of precedence for .env files in Create React App?
CRA loads files in a specific sequence where later files override earlier ones: first .env, then .env.local, followed by .env.development or .env.production (depending on the current NODE_ENV), and finally .env.development.local or .env.production.local. This hierarchy allows you to define default values in .env while overriding them with environment-specific or local-only settings.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →