ProgrUmar Logo
Module 10: Deployment & DevOps

Environment Variables & Secrets Management

Duration: 11 mins

Next.js Env Var Conventions

Variables prefixed with NEXT_PUBLIC_ are inlined into the client bundle — never put secrets there. Use the .env.local file locally and inject secrets via your hosting platform's environment settings in production.

Environment Variables & Secrets Management

A production application must interact with external databases, payment processors, and third-party authentication systems. Accessing these services requires sensitive credentials, such as API private keys or database passwords. Storing these values directly inside your source code is a major security risk that can lead to leaked databases or compromised API credentials. In this lesson, we will cover how to manage environment variables safely in Next.js, separate server-side secrets from public client variables, validate keys at startup using Zod, and configure runtime security settings.


1. Next.js Env File Priority and Resolution Order

Next.js provides built-in support for loading environment variables from files. Next resolves these files using a set priority order, loading values from top to bottom and skipping keys if they are already defined:

  1. .env.development.local or .env.production.local (Local overrides for specific environments - never commit these to Git).
  2. .env.local (Local overrides for all environments - never commit this to Git).
  3. .env.development or .env.production (Environment-specific settings - safe to commit if they only contain non-sensitive configurations).
  4. .env (Default settings loaded across all environments - safe to commit if it only contains non-sensitive configurations).

2. The NEXT_PUBLIC_ Prefix Rule

Next.js strictly partitions environment variables based on execution contexts to prevent security leaks:

  • Server-Only Variables: Variables defined without a prefix (e.g. DATABASE_URL, STRIPE_SECRET_KEY) are only accessible inside Node.js environments (Server Components, Route Handlers, Server Actions). If a Client Component attempts to read them, they return undefined.
  • Client-Public Variables: Variables prefixed with NEXT_PUBLIC_ (e.g. NEXT_PUBLIC_API_URL) are inlined into the client-side JavaScript bundle during build compile runs. They are visible to anyone inspecting your source files in the browser.

CRITICAL SECURITY WARNING: Never prefix payment secret keys, database credentials, or email server passwords with NEXT_PUBLIC_.


3. Validating Environment Variables at Startup with Zod

If you deploy your application with missing API keys (for example, forgetting to configure a Stripe secret), your app will crash when users attempt to complete checkout flows.

To prevent this, validate your environment variables at compile/startup time using Zod, causing the build to fail early with descriptive error logs if keys are missing:

// src/env.ts
import { z } from 'zod';

const envSchema = z.object({
  // Require database link string
  DATABASE_URL: z.string().url(),
  
  // Require Stripe secret key
  STRIPE_SECRET_KEY: z.string().min(1),
  
  // Validate public configurations
  NEXT_PUBLIC_API_URL: z.string().url(),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});

// Parse variables against schema
const result = envSchema.safeParse({
  DATABASE_URL: process.env.DATABASE_URL,
  STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
  NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
  NODE_ENV: process.env.NODE_ENV,
});

if (!result.success) {
  console.error('CRITICAL ERROR: Invalid environment variables configuration:', result.error.format());
  throw new Error('Invalid environment variables configuration.');
}

// Export type-safe validated environment variables
export const env = result.data;

Import env inside your application files to ensure type-safe, validated property accesses:

// Example server component use
import { env } from '@/env';

export async function checkStripeStatus() {
  // env.STRIPE_SECRET_KEY is guaranteed to be defined and type-safe
  console.log(env.STRIPE_SECRET_KEY); 
}

4. Secrets Management in Production Host Environments

In production, do not upload your .env files to hosting platforms. Instead, inject variables dynamically:

  • Vercel Settings: Configure variables inside your project's **Settings > Environment Variables** panel. Vercel encrypts these values at rest.
  • Docker / Container Settings: Pass variables as container arguments when booting up your images:
    docker run -e DATABASE_URL="mysql://..." -e STRIPE_SECRET_KEY="..." -p 3000:3000 progrumar-app
  • Kubernetes Secrets: Use Kubernetes Secret manifests to inject keys as environment variables directly into your pod configurations.

5. Mitigating Static Page Cache Environmental Desyncs

If a page is statically pre-rendered during build time (such as page.tsx containing static text), the compiler bakes the active build-time value of process.env variables directly into the static HTML files.

If you deploy that compiled build and later swap your runtime environment variables inside your hosting platform settings, the static page will continue to render the old value because Next.js has already hardcoded it during compilation.

Fix: For values that change dynamically without requiring rebuild compiles, make the page dynamic (e.g. by exporting const dynamic = 'force-dynamic') to ensure the server reads the current runtime values of process.env on every request.


6. Common Gotchas

  • Checking Secrets into Git Repositories: Forgetting to list .env*.local inside your .gitignore configuration, leading to API keys being uploaded to public GitHub repositories. If this occurs, rotate the compromised secret keys immediately.
  • Prefixing Secrets with NEXT_PUBLIC_: Attempting to read a secret key in a client component by adding NEXT_PUBLIC_ to it. This exposes your secret key publicly in the browser console.

Key Takeaways

  • List local env override files inside your .gitignore to prevent security leaks.
  • Strictly reserve the NEXT_PUBLIC_ prefix for non-sensitive client configuration parameters.
  • Use Zod to validate environment variables on startup and catch configuration errors early.
  • Inject production API credentials directly via your hosting dashboard settings or container arguments.
  • Enforce dynamic route evaluations on pages referencing variables that change frequently.

Exposing environment variables safely completes your core application DevOps configurations. Now that your backend APIs, databases, authentication middleware, state handlers, test suites, and deployment pipelines are established, you are ready to construct a real-world project. In the next module, we kick off the **Capstone Project**, mapping out the architecture and data models for a **Full-stack Blog Platform**.

Chat with us