Next.js
Environment Variables
Learn how to securely manage API keys, database URLs, and configuration settings across development, staging, and production environments in your NewsWave project.
Environment variables are your secret keepers. They hold sensitive information like API keys, database passwords, and configuration settings outside your code. Think of them as a secure vault that your application can access but hackers reading your GitHub repository cannot. Unlike plain React where you might hardcode values or use a simple config file, Next.js provides a sophisticated environment variable system. Your app runs on both the server and the client, which means some secrets should only exist on the server while others need to reach the browser safely. Every professional web application needs environment variables. NewsWave will have API keys for news services, database credentials, authentication secrets, and different settings for development versus production. Without proper environment variable management, you either expose secrets publicly or create a maintenance nightmare with hardcoded values scattered throughout your codebase.Types of Environment Variables
Next.js categorizes environment variables into three distinct types. Each serves a different purpose and has different security implications. Understanding these categories prevents you from accidentally exposing sensitive data to the browser. Server-only variables contain your most sensitive secrets. Database passwords, API keys for external services, and authentication tokens belong here. These variables only exist during server-side rendering, API routes, and build-time functions likegetServerSideProps.
// Server-only variables - never sent to browser
const databaseUrl = process.env.DATABASE_URL; // Secret database connection
const newsApiKey = process.env.NEWS_API_KEY; // Private API key
const jwtSecret = process.env.JWT_SECRET; // Authentication secretNEXT_PUBLIC_ and typically contain configuration that users can safely see. Think of them as your app's public settings.
// Public variables - safe for browser access
const appName = process.env.NEXT_PUBLIC_APP_NAME; // "NewsWave"
const apiBase = process.env.NEXT_PUBLIC_API_BASE_URL; // "https://api.newswave.com"
const gaTrackingId = process.env.NEXT_PUBLIC_GA_TRACKING_ID; // Analytics ID// Build-time only variables
const analyzeBundle = process.env.ANALYZE; // Enable bundle analyzer
const buildEnv = process.env.NODE_ENV; // "development" or "production"
const deploymentUrl = process.env.VERCEL_URL; // Auto-generated by VercelEnvironment Files Structure
Next.js loads environment variables from multiple files in a specific order. Each file serves different deployment stages, from local development to production. This hierarchy lets you override general settings with environment-specific ones. The loading order matters because later files override earlier ones. Next.js starts with the most general file and gets more specific, ending with local overrides that should never be committed to version control..env file contains variables shared across all environments. Database table names, feature flags, and other constants that rarely change belong here. This file gets committed to version control because it contains no secrets.
# .env - Default values for all environments
NEXT_PUBLIC_APP_NAME=NewsWave
NEXT_PUBLIC_APP_VERSION=1.0.0
ARTICLES_PER_PAGE=10
CACHE_DURATION=3600# .env.development - Development overrides
DATABASE_URL=postgresql://localhost:5432/newswave_dev
NEWS_API_KEY=dev_test_key_12345
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000/api# .env.production - Production settings
DATABASE_URL=postgresql://prod-server:5432/newswave_prod
NEWS_API_KEY=live_api_key_xyz789
NEXT_PUBLIC_API_BASE_URL=https://newswave.com/api.env.local file contains your personal development secrets. Database passwords, API keys, and authentication tokens that only you should know live here. This file never gets committed to version control.
# .env.local - Personal secrets (never commit)
DATABASE_PASSWORD=my_secret_password_123
JWT_SECRET=super_secret_jwt_key_456
ADMIN_EMAIL=admin@newswave.localUsing Environment Variables in Code
Environment variables behave differently depending on where you access them. Server-side code can read any variable, while client-side code only sees variables prefixed withNEXT_PUBLIC_. This restriction prevents accidental exposure of sensitive data.
Server components and API routes have access to all environment variables. Here you can safely use database credentials, private API keys, and other secrets because this code never reaches the browser.
// app/api/articles/route.js - Server-side API route
export async function GET() {
const newsApiKey = process.env.NEWS_API_KEY; // Server-only secret
const dbUrl = process.env.DATABASE_URL; // Database connection string
// Safe to use secrets here - code runs on server
const articles = await fetchFromNewsAPI(newsApiKey);
return Response.json(articles);
}undefined because Next.js strips them out during the build process to prevent security leaks.
// app/components/Header.js - Client component
'use client';
export default function Header() {
const appName = process.env.NEXT_PUBLIC_APP_NAME; // ✅ Works - public variable
const apiKey = process.env.NEWS_API_KEY; // ❌ undefined - server-only
return (
<header>
<h1>{appName || 'NewsWave'}</h1> {/* Falls back if undefined */}
</header>
);
}Next.js automatically filtered out server-only variables from the client bundle. Only public variables prefixed with NEXT_PUBLIC_ made it to the browser. Try this: Check your browser's developer console - you won't see any server-only environment variables.
Runtime Configuration vs Build-Time Variables
Environment variables load at different times depending on their type and usage. Understanding when variables become available helps you debug issues and optimize performance. Some variables get baked into your build, while others remain dynamic at runtime. Build-time variables get processed when Next.js builds your application. Public variables fall into this category because Next.js needs to replace them with actual values in your client-side code. Once built, these values cannot change without rebuilding.// These values get replaced at build time
const config = {
appName: process.env.NEXT_PUBLIC_APP_NAME, // Becomes "NewsWave" in built code
version: process.env.NEXT_PUBLIC_VERSION, // Becomes "1.0.0" in built code
apiUrl: process.env.NEXT_PUBLIC_API_URL // Becomes actual URL in built code
};// app/api/config/route.js - Runtime environment access
export async function GET() {
// These values are read fresh on each request
const currentConfig = {
databaseUrl: process.env.DATABASE_URL, // Runtime value
newsApiKey: process.env.NEWS_API_KEY, // Runtime value
cacheTimeout: process.env.CACHE_TIMEOUT // Runtime value
};
// Configuration can change without rebuilding
return Response.json({ status: 'configured', timestamp: Date.now() });
}// lib/env.js - Environment validation
function validateEnv() {
const required = ['DATABASE_URL', 'NEWS_API_KEY']; // Required server vars
const publicRequired = ['NEXT_PUBLIC_APP_NAME']; // Required public vars
// Check server-side required variables
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}Security Best Practices
Environment variable security protects your application from data breaches and unauthorized access. A single exposed API key can compromise your entire system, making security practices non-negotiable for production applications. Never commit secrets to version control. Git repositories are often public or accessible to multiple developers. Even private repositories can be compromised. Use.gitignore to exclude files containing sensitive information.
# .gitignore - Exclude sensitive files
.env.local # Personal development secrets
.env.production # Production secrets (if they exist locally)
.env.*.local # Any environment-specific local overrides// Security patterns for NewsWave
const env = {
// Separate credentials per environment
database: {
dev: process.env.DEV_DATABASE_URL,
prod: process.env.PROD_DATABASE_URL
},
// Scoped API keys
newsApi: {
testKey: process.env.NEWS_API_TEST_KEY, // Limited permissions
prodKey: process.env.NEWS_API_PROD_KEY // Full permissions
}
};// Safe logging practices
console.log('Database connected'); // ✅ Safe
console.log(`API key: ${apiKey}`); // ❌ Dangerous - logs secret
// Better error handling
try {
await connectToDatabase(process.env.DATABASE_URL);
} catch (error) {
console.log('Database connection failed'); // ✅ No secret exposed
// Log error details separately without environment values
}Never use NEXT_PUBLIC_ for secrets. These variables are visible to anyone who visits your website. Only use public variables for truly public configuration like app names or feature flags.
Deployment Environment Variables
Deployment platforms handle environment variables differently than local development. Vercel, Netlify, and other hosts provide web interfaces for managing production secrets. Understanding platform-specific patterns ensures smooth deployments. Vercel automatically provides certain environment variables during deployment. These system variables give you information about the deployment environment, Git commit, and domain names. You can use them alongside your custom variables.// Vercel provides these automatically
const deploymentInfo = {
url: process.env.VERCEL_URL, // Deployment URL
env: process.env.VERCEL_ENV, // "development", "preview", or "production"
region: process.env.VERCEL_REGION, // Deployment region
gitCommit: process.env.VERCEL_GIT_COMMIT_SHA // Git commit hash
};// app/api/config/route.js - Environment-aware configuration
export async function GET() {
const isProduction = process.env.VERCEL_ENV === 'production';
const isPreview = process.env.VERCEL_ENV === 'preview';
const config = {
environment: process.env.VERCEL_ENV,
databaseUrl: isProduction
? process.env.PROD_DATABASE_URL
: process.env.STAGING_DATABASE_URL,
features: {
analytics: isProduction, // Only in production
debugging: !isProduction // Only in development/preview
}
};
return Response.json(config);
}Your app automatically adapts its configuration based on the deployment environment. Production gets live databases and analytics, while preview deployments use staging resources. This prevents test traffic from affecting real users. Try this: Deploy NewsWave to Vercel and check how environment variables change between preview and production deployments.
Quiz
1. The NewsWave team needs to store both a database password and the app name. What's the key difference between server-only environment variables and NEXT_PUBLIC_ variables?
2. Which environment file should contain your personal API keys and passwords that should never be committed to version control?
3. In a NewsWave client component, what happens when you try to access process.env.DATABASE_URL (a server-only variable)?
Up Next: Performance Optimization
Master code splitting, image optimization, caching strategies, and Core Web Vitals to make NewsWave lightning-fast for millions of readers.