Handling Missing Environment Variables in React Applications
Proper fallback mechanisms for environment variables are crucial to prevent runtime errors when variables are undefined. Here’s a comprehensive guide to implementing robust environment variable handling.
Common Issues Without Proper Fallbacks
- Application crashes when variables are missing
- Undefined behavior in production
- Security risks from falling back to development values
- Debugging challenges due to silent failures
Recommended Implementation Patterns
1. Basic Fallback with Default Values
// config.js
const API_URL = process.env.REACT_APP_API_URL || 'https://api.dev.example.com';
const DEBUG_MODE = process.env.REACT_APP_DEBUG === 'true' || false;
export { API_URL, DEBUG_MODE };
2. Type-Safe Validation (TypeScript)
// env.ts
interface Env {
API_BASE: string;
SENTRY_DSN: string;
}
const validateEnv = (): Env => {
const API_BASE = process.env.REACT_APP_API_BASE;
if (!API_BASE) throw new Error('REACT_APP_API_BASE is required');
return {
API_BASE,
SENTRY_DSN: process.env.REACT_APP_SENTRY_DSN || ''
};
};
export const env = validateEnv();
3. Environment-Specific Defaults
// envConfig.js
const getEnvConfig = () => {
const isProd = process.env.NODE_ENV === 'production';
return {
apiKey: process.env.REACT_APP_API_KEY ||
(isProd ? '' : 'dev-key-123'), // Never fallback to dev key in prod
analyticsId: process.env.REACT_APP_GA_ID || null,
enableDebug: !isProd // Auto-enable debug in non-production
};
};
export default getEnvConfig();
Production-Grade Solutions
1. Build-Time Validation (Webpack Plugin)
// webpack.config.js
const { DefinePlugin } = require('webpack');
const requiredEnvVars = ['API_BASE', 'AUTH_DOMAIN'];
module.exports = {
plugins: [
new DefinePlugin(
requiredEnvVars.reduce((acc, varName) => {
if (!process.env[`REACT_APP_${varName}`]) {
throw new Error(`Missing required env var: REACT_APP_${varName}`);
}
acc[`process.env.REACT_APP_${varName}`] =
JSON.stringify(process.env[`REACT_APP_${varName}`]);
return acc;
}, {})
]
};
2. Runtime Validation Hook
// useEnv.js
import { useEffect } from 'react';
const requiredVars = ['REACT_APP_API_BASE'];
const useEnvValidation = () => {
useEffect(() => {
const missingVars = requiredVars.filter(v => !process.env[v]);
if (missingVars.length > 0) {
console.error('Missing environment variables:', missingVars);
// Optionally redirect to error page
}
}, []);
};
export default useEnvValidation;
Deployment Strategies
1. Docker Entrypoint Validation
#!/bin/sh
# docker-entrypoint.sh
REQUIRED_VARS="REACT_APP_API_BASE REACT_APP_ENV"
for var in $REQUIRED_VARS; do
if [ -z "${!var}" ]; then
echo "ERROR: $var is not set"
exit 1
fi
done
exec "$@"
2. CI/CD Pipeline Checks
# GitHub Actions example
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check for required env vars
run: |
REQUIRED_VARS=("API_BASE" "AUTH_DOMAIN")
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
echo "::error::Missing $var"
exit 1
fi
done
Best Practices
- Never expose development defaults in production
- Validate early – fail fast during build or initialization
- Use typing for TypeScript projects
- Document requirements in README.md
- Consider using a configuration service for production
- Implement monitoring for configuration errors
Error Recovery Patterns
// Graceful degradation example
const paymentConfig = {
stripeKey: process.env.REACT_APP_STRIPE_KEY || null,
paymentDisabled: !process.env.REACT_APP_STRIPE_KEY
};
// In your component
if (paymentConfig.paymentDisabled) {
return <PaymentUnavailable />;
}
By implementing these patterns, you ensure your application handles missing environment variables gracefully while maintaining security and reliability across all environments.