Environment variables are essential for modern web development. They allow you to configure your app for different environments (development, staging, production) without changing the source code.

But while they're straightforward to use in backend code, they can be misused or misunderstood in frontend applications.


๐ŸŒ What are environment variables?

Environment variables are key-value pairs injected into your app's runtime environment. They're used to store configuration, not code.

Examples:

NODE_ENV=production
DATABASE_URL=postgres://user:pass@host/db
API_URL=https://api.myapp.com

These values are not hardcoded in your source files, they're passed in from .env files, deployment platforms, or shell scripts.


๐Ÿ–ฅ๏ธ Backend: node.js, express, etc.

โœ… How to use them

In Node.js apps, environment variables are commonly loaded using dotenv:

# .env
PORT=4000
SECRET_KEY=mysecret
DATABASE_URL=postgres://user:pass@localhost/mydb
// server.js
require('dotenv').config();

const express = require('express');
const app = express();

app.listen(process.env.PORT, () => {
  console.log(`Server running on port ${process.env.PORT}`);
});

๐Ÿ“Œ Best practices

  • โœ… Store secrets (tokens, DB credentials) in env vars
  • โœ… Use .env.local for development-specific config
  • โœ… Use process.env.VARIABLE directly, not hardcoded values
  • โœ… Validate required variables before starting your app
// Validate environment variables at startup
const requiredEnvVars = ['PORT', 'SECRET_KEY', 'DATABASE_URL'];

for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`);
  }
}

Tip: Fail fast, don't let your app start if essential config is missing.


๐Ÿง‘โ€๐Ÿ’ป Frontend: react, vite, next.js, etc.

โš ๏ธ Important difference

Frontend code runs in the browser, so not all env vars are safe to expose.

When you build your frontend app, build tools like Vite, Create React App, or Next.js only include environment variables with specific prefixes in the client-side bundle.

FrameworkPublic Prefix RequiredExample
ViteVITE_VITE_API_URL
Create React AppREACT_APP_REACT_APP_API_URL
Next.jsNEXT_PUBLIC_NEXT_PUBLIC_API_URL
# .env
VITE_API_URL=https://api.example.com
VITE_ANALYTICS_ID=GA-123456789
// inside a React/Vite component
const apiUrl = import.meta.env.VITE_API_URL;
fetch(`${apiUrl}/users`);

๐Ÿ”„ How frontend build process works

During the build process, your build tool replaces environment variables with their actual values:

// Your source code:
fetch(import.meta.env.VITE_API_URL + '/users')

// Final bundled code:
fetch('https://api.example.com/users')

This means anyone can see these values by inspecting your JavaScript bundle.

โš ๏ธ Never expose secrets

Any env var used in the frontend is visible to anyone who opens DevTools or views your source code.

โœ… Safe for frontend:

  • VITE_API_URL
  • REACT_APP_ANALYTICS_ID
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

โŒ Never in frontend:

  • VITE_SECRET_KEY
  • REACT_APP_DB_PASSWORD
  • NEXT_PUBLIC_STRIPE_SECRET_KEY

๐Ÿ“ฑ Next.js special considerations

Next.js has additional complexity due to its hybrid nature:

// Server-side only (API routes, getServerSideProps)
const secretKey = process.env.SECRET_KEY; // No prefix needed

// Client-side (components, pages)
const publicUrl = process.env.NEXT_PUBLIC_API_URL; // Prefix required

Runtime vs Build-time: Next.js can access server-side env vars at runtime, but client-side vars are embedded at build time.


๐Ÿ›ก๏ธ Protecting secrets

โœ… Keep secrets server-side only

For example, in a full-stack app:

  • Use VITE_API_URL in the frontend to talk to your own backend
  • Keep the SECRET_KEY, DB credentials, and third-party tokens in the backend .env

If your frontend needs to interact with a third-party API requiring authentication, proxy it through your backend:

// Backend API route
app.get('/api/external-data', async (req, res) => {
  const response = await fetch('https://api.third-party.com/data', {
    headers: {
      'Authorization': `Bearer ${process.env.SECRET_API_KEY}`
    }
  });
  const data = await response.json();
  res.json(data);
});

// Frontend code
fetch('/api/external-data') // No secrets exposed
  .then(response => response.json())
  .then(data => console.log(data));

๐Ÿ“ฆ Managing env files

Recommended file structure:

.env             # Default values for all environments
.env.development # Development-specific overrides
.env.staging     # Staging-specific overrides
.env.production  # Production-specific overrides
.env.local       # Machine-specific config (never committed)

๐Ÿ“‹ File precedence order

Environment files are loaded in this order (highest priority first):

  1. .env.local (highest priority)
  2. .env.development / .env.staging / .env.production
  3. .env (lowest priority)

๐Ÿงผ Git ignore

Always add sensitive files to your .gitignore:

# .gitignore
.env.local
.env*.local
.env.production

Note: You might want to commit .env.development and .env with safe default values, but never commit files with real secrets.


๐Ÿง  Common mistakes

MistakeWhy It's BadHow to Fix
Exposing secrets in frontendThey're readable by anyoneKeep secrets in backend only
Hardcoding URLs in codeCan't switch environments easilyUse env vars with proper prefixes
Using wrong prefix (e.g. API_URL)Won't be available in frontend bundleUse VITE_, NEXT_PUBLIC_, etc
Committing .env with secretsLeaks credentials in version controlUse .gitignore properly
Not validating required variablesApp crashes at runtimeValidate at startup
Mixing build-time and runtime varsUnexpected behavior in productionUnderstand when vars are resolved

๐Ÿ” Debugging environment variables

Check what's available:

// Backend
console.log('All env vars:', process.env);

// Frontend (Vite)
console.log('Available env vars:', import.meta.env);

// Frontend (Create React App)
console.log('Available env vars:', process.env);

Common debugging steps:

  1. Check file naming: .env not env.txt
  2. Verify prefixes: VITE_API_URL not API_URL
  3. Restart dev server: Changes require restart
  4. Check file location: .env should be in project root
  5. No quotes needed: API_URL=https://example.com not API_URL="https://example.com"

โœ… Summary

  • Use environment variables to keep config out of your code
  • Backend: Use process.env directly, validate required vars, never hardcode secrets
  • Frontend: Only expose non-sensitive values with the correct prefix (VITE_, REACT_APP_, NEXT_PUBLIC_)
  • Security rule: If it's used in the frontend, it's public to everyone
  • File management: Use .env.local for secrets, .gitignore sensitive files
  • Production: Configure env vars through your deployment platform

๐Ÿ”š Final thoughts

Environment variables are a simple but powerful tool. When used correctly, they keep your apps secure, portable, and configurable, whether you're deploying to Vercel, Docker, or bare metal.

Just remember the golden rule:

If it's used in the frontend, it's public. If it's sensitive, keep it on the server.

With proper understanding of how environment variables work in both frontend and backend contexts, you can build applications that are both secure and maintainable across different environments.