Skip to main content
Week 06 • Track 03 • The Process

Secrets & Security.

Your API keys are the master keys to your app. One accidental commit and bots will find them in seconds. Let's lock the vault.

checkout.ts
LEAKED!

const stripe = require('stripe')(

'sk_live_51N8x...a3Fj9kZpR'

);

const session = await stripe.checkout

.sessions.create({

mode: 'payment',

line_items: [{ price: 'price_1N...' }],

})

⚠ API key hardcoded in source code

⚠ Will be visible in Git history forever

⚠ Bots scan GitHub for 'sk_live' every second

Anyone who sees your repo sees your key.

The Mental Model

Not all keys are created equal.

Think of your app like a concert venue. Some keys let people in the front door. Others open the vault backstage. Treat them differently.

Public Key = The Name Badge

OK to show. Identifies you but can't do damage. Like NEXT_PUBLIC_STRIPE_KEY.

Secret Key = The Master Key

Never share. Lets anyone charge cards, send emails, access your database. Guard it with your life.

Environment Variable = The Vault

Where keys live at runtime. Never in your code. Never in Git. Only on the server.

.gitignore = The Bouncer

Prevents your vault file from ever being committed to Git.

.env.local

# Public keys (safe in browser)

NEXT_PUBLIC_STRIPE_KEY=pk_test_51N8x...

# Secret keys (server only!)

STRIPE_SECRET_KEY=sk_test_51N8x...

OPENAI_API_KEY=sk-proj-abc123...

RESEND_API_KEY=re_1a2b3c...

# Database

DATABASE_URL=postgresql://user:pass@...

NEXT_PUBLIC_* = client-safe everything else = server-only

The .env File

Where your secrets live.

Next.js loads environment variables from .env files automatically. But not all .env files are equal.

The hierarchy matters. More specific files override general ones. And crucially, .env.local is automatically added to .gitignore by Next.js.

1

.env

All environments. Committed to Git. No secrets here.

2

.env.local

Local only. Gitignored. This is where your secrets go.

3

.env.production

Production overrides. Use for non-secret production config.

4

.env.development

Dev overrides. Useful for local-only defaults.

.env.local

# ============================================

# NEXT_PUBLIC_* variables are exposed to the

# browser. Only use for non-sensitive data.

# ============================================

NEXT_PUBLIC_APP_URL=http://localhost:3000

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

# ============================================

# Server-only variables. NEVER prefix with

# NEXT_PUBLIC_ or they leak to the client.

# ============================================

STRIPE_SECRET_KEY=sk_test_...

STRIPE_WEBHOOK_SECRET=whsec_...

OPENAI_API_KEY=sk-proj-...

RESEND_API_KEY=re_...

DATABASE_URL=postgresql://...

Priority: .env.local > .env.development > .env

Test Mode vs. Live Mode

🧪

Test Mode

Safe

A sandbox where nothing is real. No actual charges. No real emails sent. Perfect for development.

// Stripe test keys start with:

pk_test_... // publishable

sk_test_... // secret

// Test card number:

4242 4242 4242 4242

Any future date, any CVC

No real money moves
Fake card numbers work
Use freely during development
💳

Live Mode

Real Money

The real deal. Actual charges on real credit cards. Only use in production. Never in development.

// Stripe live keys start with:

pk_live_... // publishable

sk_live_... // secret

// Set in production only:

STRIPE_SECRET_KEY=sk_live_...

Set via Vercel dashboard, not .env

Real charges on real cards
Mistakes cost actual money
Only deploy after thorough testing

The Danger Scale

SAFE
DANGER
pk_test_*sk_live in Git

The .gitignore Shield

The file that keeps secrets out of Git.

.gitignore tells Git which files to pretend don't exist. If a file matches a pattern in .gitignore, it will never be committed.

Next.js adds .env*.local to .gitignore by default. But you should always double-check.

If you accidentally commit a key…

GitHub will email you a security alert within seconds. Bots scan every public commit for patterns like sk_live, sk-proj, and password=.

💡

Deleting the file isn't enough

The key is still in your Git history. You'd need to rotate the key immediately and rewrite history with git filter-branch.

Prevention is the only cure

Always verify .gitignore before your first commit. Run git status and confirm .env files don't appear.

.gitignore

# dependencies

/node_modules

# next.js

/.next/

/out/

# production

/build

# environment variables

.env*.local

# misc

.DS_Store

*.tsbuildinfo

next-env.d.ts

✓ .env*.local is gitignored by default in Next.js

Always verify before your first push

Common Patterns

Security Traps

"I'll just paste the key inline"

Hardcoded keys end up in Git. Even if you delete them later, they're in the history forever.

"I committed .env but it's fine"

GitHub bots scan every push. Your key will be found and exploited within minutes.

"I'll use NEXT_PUBLIC_ for my secret key"

NEXT_PUBLIC_ vars are bundled into client-side JavaScript. Anyone can read them in DevTools.

"I use live keys in development"

One test gone wrong and you charge a real card. Always use test keys locally.

The Vault

Use process.env for all keys

Keys live in .env.local, loaded at runtime. Never in source code.

Verify .gitignore first

Before your first commit, run git status and confirm .env files aren't tracked.

NEXT_PUBLIC_ only for non-secrets

Publishable keys, app URLs, feature flags. Never secret keys or tokens.

Set production keys in Vercel dashboard

Go to Settings → Environment Variables. Keys are encrypted and injected at build time.

The Exercises

Lock down your secrets.

01

The Vault Setup

Create & Protect

  • Create a .env.local file in your project root
  • Add a test API key: MY_SECRET=hello123
  • Verify .gitignore includes .env*.local
Goal: .env.local is invisible to Git
02

The Access Pattern

Server vs. Client

  • Access the key in a server component via process.env.MY_SECRET
  • Try accessing it in a client component
  • Confirm it's undefined on the client
Goal: Server-only vars stay on the server
03

The Audit

Check Your History

  • Run git log --all -p | grep -i "key\|secret\|password"
  • Check if any real credentials appear in your Git history
  • If found: rotate the key immediately, then clean history
Goal: Zero leaked secrets in your repo

📋 Quick Reference

Access a Secret

process.env.STRIPE_SECRET_KEY

Client-Safe Variable

NEXT_PUBLIC_APP_URL=...

Audit for Leaks

git log -p | grep -i secret