Skip to main content

Environment Variables & Security Management

Environment variables are like the keys to a safe — you need the key to open it, but you wouldn't tape the key to the safe itself.

What Are Environment Variables?

Imagine you have a lock and a key:

  • Lock = The configuration your app needs (database URL, API keys, etc.)
  • Key = The values stored in environment variables
  • Safe = The .env file

Environment variables are configuration values stored outside your code. Your code reads them by "variable name," but the actual values never appear in the code itself.

Why?

// ❌ Hardcoding keys in code (dangerous!)
const apiKey = "sk-123...cdef";
// Push this to GitHub and everyone can see your key

// ✅ Using environment variables (safe!)
const apiKey = process.env.OPENAI_API_KEY;
// The key lives on the server, the code only has the variable name

.env File Format

A .env file is just a plain text file with one configuration per line:

# .env file
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
OPENAI_API_KEY=sk-123...cdef
API_SECRET=my-super-secret-key

Format Rules

# ✅ Correct format
KEY=value
KEY="value with spaces"
KEY='also works with single quotes'

# ❌ Incorrect format
KEY = value # No spaces around the equals sign
KEY="value" # Quotes only needed when value has spaces

Comments

# This is a comment
DATABASE_URL=postgresql://... # This is also a comment

.env File Variants

Next.js supports multiple .env files:

FilePurposeShould You Commit It?
.envDefault values for all environments✅ Yes
.env.localLocal overrides, contains secrets❌ Never
.env.developmentDevelopment-only values✅ Yes
.env.productionProduction-only values✅ Yes
.env.exampleTemplate showing required keys (no real values)✅ Yes

Loading priority: .env.development.local > .env.local > .env.development > .env

The Two Most Common Files

## .env File Variants

Next.js supports multiple `.env` files:

| File | Purpose | Should You Commit It? |
|------|---------|----------------------|
| `.env` | Default values for all environments | ✅ Yes |
| `.env.local` | Local overrides, contains secrets | ❌ Never |
| `.env.development` | Development-only values | ✅ Yes |
| `.env.production` | Production-only values | ✅ Yes |
| `.env.example` | Template showing required keys (no real values) | ✅ Yes |

Loading priority (highest to lowest): `.env.development.local` > `.env.local` > `.env.development` > `.env`

### The Two Most Common Files

```bash
# .env.local — your actual secrets (NEVER commit)
DATABASE_URL=postgresql://user:***@localhost:5432/mydb
OPENAI_API_KEY=sk-123...n
# .env.example — template for others, no real values
DATABASE_URL=postgresql://user:***@localhost:5432/mydb
OPENAI_API_KEY=your-openai-api-key-here

.gitignore Must Include .env

This is the most important rule: never commit .env files containing real keys to Git.

# .gitignore

# Environment variable files (must be ignored)
.env
.env.local
.env.*.local

# Other common ignores
node_modules/
.next/

What If You Accidentally Committed It?

# 1. Remove from Git history (but keep the file locally)
git rm --cached .env.local

# 2. Make sure .gitignore includes it
echo ".env.local" >> .gitignore

# 3. Commit
git commit -m "chore: remove .env.local, update .gitignore"

# 4. If already pushed to remote, keys may be compromised
# Immediately go to the relevant platform (Supabase, OpenAI, etc.) and regenerate keys!

Important: Even after deleting the file, it's still visible in Git history. If keys have been pushed, regenerating them immediately is the only safe course of action.

How Next.js Handles Environment Variables

The NEXT_PUBLIC_ Prefix

Next.js has an important rule for environment variables:

# Available server-side only (default)
DATABASE_URL=postgresql://...
OPENAI_API_KEY=sk-...

# Also available client-side (must have NEXT_PUBLIC_ prefix)
NEXT_PUBLIC_APP_NAME=My App
NEXT_PUBLIC_API_URL=https://api.example.com

Rules:

  • Variables without prefix: only available on the server (API Routes, Server Components)
  • Variables with prefix: available on both server and client
# ✅ Safe — only visible server-side
OPENAI_API_KEY=sk-...
DATABASE_URL=postgresql://...

# ❌ Dangerous — exposed to the browser!
NEXT_PUBLIC_API_KEY=sk-...

Key point: Any variable starting with will be bundled into the client-side JavaScript. Never put secrets like API keys behind this prefix.

Naming conventions

  • Use for all environment variables
  • Group related variables with prefixes: ,
  • Default values use :

Setting Up a Real Project

Here's a step-by-step guide for a Next.js + Supabase + OpenAI project:

1. Create

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# Supabase (get from supabase.com -> Settings -> API)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key

# OpenAI API Key (get from platform.openai.com)
OPENAI_API_KEY=your-openai-api-key

# App config
NEXT_PUBLIC_APP_URL=http://localhost:3000

2. Create

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# OpenAI API Key (get from platform.openai.com)
OPENAI_API_KEY=your-openai-api-key

# Supabase (get from supabase.com -> Settings -> API)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key

3. Update

# Add to .gitignore
.env.local
.env.*.local

4. Generate Secure Random Keys

# Generate a random string with Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# Using OpenSSL
openssl rand -hex 32

Common Mistakes and Security Risks

❌ Mistake 1: Committing .env to Git

# Check if sensitive files are being tracked
git status
# If .env.local shows up, stop immediately!

# Remove from tracking
git rm --cached .env.local

❌ Mistake 2: Using Sensitive Variables in Client Code

// ❌ Dangerous! This value gets bundled into browser code
const apiKey = process.env.NEXT_PUBLIC_API_KEY;
// Users can see it by inspecting the page source

// ✅ Correct! Route through an API endpoint
// app/api/chat/route.ts
export async function POST(req: Request) {
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY, // Server-side only
});
// ...
}

❌ Mistake 3: Hardcoding Keys in Code

// ❌ Never do this
const db = new Database('postgresql://root:password@localhost/db');

// ✅ Use environment variables
const db = new Database(process.env.DATABASE_URL);

❌ Mistake 4: Exposing Keys in Screenshots/Screen Recordings

When demoing your project:

  • Close the file before showing anything
  • Don't capture terminal output with environment variables
  • Don't record the Vercel settings page

Hands-On: Configuring Supabase + OpenAI Keys

Let's walk through the complete workflow:

1. Get Supabase Keys

  1. Go to supabase.com and create a project
  2. Go to SettingsAPI
  3. Copy:
    • Project URL
    • anon public key

2. Get OpenAI Keys

  1. Go to platform.openai.com
  2. Go to API Keys
  3. Click Create new secret key
  4. Copy the key (only shown once!)

3. Configure Local Environment

# Copy the template
cp .env.example .env.local

# Edit .env.local
# .env.local

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://abcdefghij.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbG...VCJ9...

# OpenAI (server-side, no NEXT_PUBLIC_ prefix)
OPENAI_API_KEY=sk-123...cdef

# App config
NEXT_PUBLIC_APP_URL=http://localhost:3000

4. Use in Code

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;

export const supabase = createClient(supabaseUrl, supabaseKey);
// app/api/chat/route.ts
import OpenAI from 'openai';

const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});

export async function POST(req: Request) {
const { message } = await req.json();

const completion = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: message }],
});

return Response.json(completion.choices[0].message);
}

5. Deploy to Vercel

# Option 1: CLI
vercel env add NEXT_PUBLIC_SUPABASE_URL
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY
vercel env add OPENAI_API_KEY

# Option 2: Website settings
# Settings -> Environment Variables -> add one by one

6. Verify

# Start locally
npm run dev

# Test environment variables in code
# app/api/test/route.ts
export async function GET() {
return Response.json({
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL ? 'Configured' : 'Not configured',
openaiKey: process.env.OPENAI_API_KEY ? 'Configured' : 'Not configured',
});
}

Visit and confirm both show Configured.

Summary

  • Environment variables = configuration outside your code — keys don't belong in code
  • Never commit .env.local to Git — this is an iron rule
  • Only NEXT_PUBLIC_ prefixed variables are exposed to the browser — never use this prefix for sensitive data
  • Use .env.example as a template for others — without real values
  • Configure manually on Vercel during deployment — local .env files don't transfer automatically
  • Regenerate keys immediately if compromised — don't take chances

Security management sounds serious, but it's really just a few simple habits. Develop these habits and your project won't make the news because of a leaked key.