Next.js Lesson 20 – Environment Variables | Dataplexa
Lesson 20

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 like getServerSideProps.
// 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 secret
Public variables are safe to expose to the browser. They must start with NEXT_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 variables exist only during the build process. These configure how Next.js builds your application but disappear from the runtime. They control features like bundle analysis, deployment settings, and build optimizations.
// 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 Vercel

Environment 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.
📁 newswave/
📄 .env.local # Local overrides (never commit)
📄 .env.development # Development environment
📄 .env.production # Production environment
📄 .env # Default for all environments
📄 .gitignore # Contains .env.local
The .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
Environment-specific files override defaults with values appropriate for their deployment stage. Development uses local databases and test API keys, while production uses real services and live credentials.
# .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
The .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.local

Using 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 with NEXT_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);
}
Server API Response
✓ Environment variables loaded
NEWS_API_KEY: ***hidden***
DATABASE_URL: ***hidden***
Fetching articles from News API...
✓ Successfully retrieved 50 articles
Client components can only access public environment variables. Attempting to read server-only variables returns 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>
  );
}
localhost:3000 — NewsWave
What just happened?

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
};
Server-side variables remain dynamic at runtime. API routes, server components, and middleware can read fresh values from the environment every time they execute. This flexibility lets you change configuration without rebuilding your application.
// 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() });
}
API Response
GET /api/config
Reading runtime environment variables...
✓ DATABASE_URL loaded
✓ NEWS_API_KEY loaded
✓ CACHE_TIMEOUT loaded
Response: {"status": "configured", "timestamp": 1698765432101}
Variable validation ensures your application has all required environment variables before starting. Next.js doesn't validate environment variables by default, but you can create a validation system that catches missing or invalid values early.
// 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
Git Status
$ git status
On branch main
Untracked files:
.env
.env.development
.env.local (ignored)
✓ Sensitive files protected by .gitignore
Rotate secrets regularly and use different credentials for each environment. Development databases should use separate credentials from production. API keys should be environment-specific when possible. This isolation prevents development mistakes from affecting production systems.
// 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
  }
};
Avoid logging environment variables to prevent accidental exposure in log files. Production logs are often stored and analyzed by multiple systems. Even server-only variables can leak through error messages or debug output.
// 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
}
Security Alert

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
};
Vercel Deployment
✓ Deployment successful
VERCEL_URL: newswave-abc123.vercel.app
VERCEL_ENV: production
VERCEL_REGION: iad1
Environment variables loaded from dashboard
Environment-specific deployments let you test different configurations without affecting production. Preview deployments on Vercel can use different environment variables than your main production site, enabling safe testing of new features or integrations.
// 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);
}
What just happened?

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.