Manage Environment Variables in Next.js

Manage Environment Variables in Next.js

Introduction
Environment variables are a crucial part of any modern web application. They allow you to store configuration details—such as API keys, database URLs, feature flags, and other sensitive or environment-specific data—outside of your codebase. Next.js, as a versatile React framework, offers first‑class support for environment variables, making it easier to manage settings across development, staging, and production environments without risking accidental exposure of secrets.

In this guide, we’ll dive into:

  1. What environment variables are and why they matter
  2. How Next.js loads environment variables
  3. File naming conventions (.env.local, .env.development, .env.production, etc.)
  4. Public vs. server‑only variables (NEXT_PUBLIC_ prefix)
  5. Accessing env vars in code (server & client)
  6. Best practices for security and maintainability
  7. Handling different environments
  8. Advanced use cases (runtime configuration, validation, feature flags)
  9. Common pitfalls and troubleshooting
  10. Conclusion

1. What Are Environment Variables and Why They Matter

Environment variables are key-value pairs defined outside of your application code. They allow you to:

  • Separate configuration from code: You can change behavior without modifying code.
  • Keep secrets secure: API keys, database credentials, and other secrets can live outside version control.
  • Enable environment‑specific settings: Switch between local development, CI, staging, and production effortlessly.
  • Make deployments more flexible: CI/CD pipelines or hosting providers can inject variables at runtime.

In Node.js, environment variables are accessible via process.env. Next.js enhances this by supporting multiple env files and letting you choose which values are exposed to the browser versus kept strictly on the server.

2. How Next.js Loads Environment Variables

Next.js follows a well-defined order when loading environment variables from .env files. By default, it looks for the following files in your project root:

  1. .env.local
  2. .env.development (if NODE_ENV=development)
  3. .env.test (if NODE_ENV=test)
  4. .env.production (if NODE_ENV=production)
  5. .env
Important:
  • .env.local is always loaded first, and never committed to version control (recommended to add to .gitignore).
  • Files specific to an environment override the generic .env.
  • Values in process environment (e.g., set in CI or hosting dashboard) take precedence over files.

Internally, Next.js uses dotenv to parse these files at build time. When you run next dev, next build, or next start, the variables are loaded according to your NODE_ENV.

3. File Naming Conventions

Next.js supports these naming patterns:

Filename Loaded When Version Control Purpose

  • .env
    • Loaded When: All environments
    • Version Control: ✅
    • Purpose: Base defaults
  • .env.local
    • Loaded When: All environments (except test)
    • Version Control: ❌
    • Purpose: Local overrides, secrets
  • .env.development
    • Loaded When: Development (NODE_ENV=development)
    • Version Control: ✅
    • Purpose: Dev-specific settings
  • .env.test
    • Loaded When: Test (NODE_ENV=test)
    • Version Control: ✅
    • Purpose: Test suite configuration
  • .env.production
    • Loaded When: Production (NODE_ENV=production)
    • Version Control: ✅
    • Purpose: Production overrides
Tip: Always include .env.local in .gitignore so you don’t accidentally commit secrets.

4. Public vs. Server‑Only Variables

By default, any variable defined in an .env* file is only available on the server side. To expose a variable to the browser, you must prefix its name with NEXT_PUBLIC_.

Scope Prefix Example Variable Name

  • Server‑only
    • Prefix: (none)
    • Example Variable Name: DATABASE_URL
  • Client‑visible
    • Prefix: NEXT_PUBLIC_
    • Example Variable Name: NEXT_PUBLIC_API_BASE

Why the prefix?

  • Prevents accidental leakage of secrets into client bundles
  • Makes it explicit which variables are safe for the browser

5. Accessing Environment Variables in Code

On the Server Side

// pages/api/hello.js

export default function handler(req, res) {
  const dbUrl = process.env.DATABASE_URL; // server-only
  res.status(200).json({ dbUrl });
}

On the Client Side

// components/ApiClient.js

const ApiClient = () => {
  React.useEffect(() => {
    console.log(process.env.NEXT_PUBLIC_API_BASE); // exposed to browser
  }, []);

  return <div>Check console for API base URL</div>;
};

export default ApiClient;

Note: Accessing process.env.MY_VAR without the NEXT_PUBLIC_ prefix in client‑side code will be undefined.

6. Best Practices for Security and Maintainability

  1. Never commit secrets to version control. Use .env.local or your hosting provider’s secure config.
  2. Document your variables in a .env.example file (without real values).
  3. Validate env vars at startup. Use libraries like joi or zod to ensure required variables exist and have the correct format.
  4. Use descriptive names: Group related variables, e.g. NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY vs. STRIPE_SECRET_KEY.
  5. Rotate secrets regularly: Treat env vars as sensitive; rotate API keys, tokens, and credentials periodically.
  6. Limit client exposure: Only prefix variables that truly need to be in the browser.

7. Handling Multiple Environments

In real‑world projects, you often have multiple environments:

  • Local development
  • Staging
  • Production

Use the file hierarchy:

  1. .env for universal defaults
  2. .env.development, .env.staging, .env.production for environment‑specific values
  3. Host‑provided variables override all .env* files in deployment (e.g., Vercel dashboard, Netlify UI, AWS Parameter Store)

Example Structure

.env
.env.development
.env.staging
.env.production
.env.local       # gitignored
.env.example     # documentation / template

8. Advanced Use Cases

8.1 Runtime Configuration with next.config.js

Sometimes you need to read environment variables at runtime rather than build time. By default, Next.js injects env vars into the static build. For truly dynamic runtime config (e.g., in a Docker container), you can:

// next.config.js

module.exports = {
  publicRuntimeConfig: {
    // available on both server and client
    apiBase: process.env.NEXT_PUBLIC_API_BASE,
  },
  serverRuntimeConfig: {
    // available only on server
    dbUrl: process.env.DATABASE_URL,
  },
};

Access via getConfig():

import getConfig from 'next/config';

const { publicRuntimeConfig, serverRuntimeConfig } = getConfig();
console.log(publicRuntimeConfig.apiBase);
Warning: publicRuntimeConfig still ends up in the client bundle. Don’t put secrets there.

8.2 Feature Flags and Toggles

You can use env vars to toggle features:

# .env.production
FEATURE_BETA_DASHBOARD=true

const isBetaEnabled = process.env.NEXT_PUBLIC_FEATURE_BETA_DASHBOARD === 'true'; if (isBetaEnabled) { // Render beta version }

Pair with a flag management service or build‑time flags for more sophisticated setups.

8.3 Validating Env Vars at Startup

// lib/validateEnv.js

import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXT_PUBLIC_API_BASE: z.string().url(),
  NODE_ENV: z.enum(['development', 'production', 'test']),
});

export const env = envSchema.parse(process.env);

Then import env everywhere to guarantee type safety.

9. Common Pitfalls and Troubleshooting

Issue Solution

  • process.env.MY_VAR is undefined in client code
    • Solution: Ensure it’s prefixed with NEXT_PUBLIC_.
  • Changes to .env aren’t taking effect
    • Solution: Restart the dev server (Ctrl+C and npm run dev).
  • Env vars not available in getStaticProps
    • Solution: They’re only available at build time; rebuild your project or consider using getServerSideProps for runtime access.
  • Secrets accidentally exposed
    • Solution: Audit your code for stray NEXT_PUBLIC_ prefixes and rotate any compromised keys.
  • Mistaking file loading order
    • Solution: Remember the loading hierarchy: .env.local → .env.[environment] → .env.

10. Conclusion

Next.js makes it straightforward to manage environment variables across different stages of development and deployment. By following the conventions for file naming, prefixing public variables, validating at startup, and keeping secrets out of your codebase, you can maintain a secure, maintainable, and flexible configuration system.

Key takeaways:

  • Use .env* files intelligently: base defaults in .env, overrides in environment‑specific files, and secrets in .env.local.
  • Prefix client‑exposed vars with NEXT_PUBLIC_.
  • Validate and document all environment variables.
  • Leverage next.config.js for runtime configuration when necessary.

With these practices in place, you’ll be confident that your Next.js application’s configuration is robust, secure, and easy to manage—no matter how many environments or secrets you have to juggle.

#Next.js environment variables #Next.js env #dotenv #.env.local #.env.development #.env.production #NEXT_PUBLIC prefix #server-only variables #client-visible variables #Next.js configuration #runtime configuration #feature flags #getStaticProps environment

Subscribe to our newsletter

12k+

Subscribers

Weekly

Frequency

Free

Always