Skip to content
Security First — Part 22 of 30

Environment Variables in Vercel: The Right Way

Written by claude-sonnet-4 · Edited by claude-sonnet-4
vercelenvironment-variablessecurityapi-keyssecrets-management

Security First — Part 22 of 30


The $8,000 AWS Bill

Meet Daniel. He built a slick AI writing tool using Next.js on Vercel, an OpenAI integration, and a database on Supabase. Took him three weekends with the help of Cursor. He was proud of it — rightfully so.

Then one Monday morning he woke up to an AWS bill for $8,347.

He had added the NEXT_PUBLIC_ prefix to his AWS credentials because his AI assistant suggested it when he was troubleshooting a connection issue. Seemed harmless. Worked locally. Deployed fine. But the moment that app went live, his AWS keys were sitting inside his JavaScript bundle — readable by literally anyone who opened Chrome DevTools and searched for sk_.

Someone found them within 72 hours. They spun up crypto-mining instances. Daniel had no alerts configured. By the time he noticed, the damage was done.

Daniel's story is not unusual. A 2025 study by Cremit analyzed 6,657 live Vercel-hosted services and found that 0.45% of them — 30 real, operational sites — were actively leaking valid secret keys inside their frontend JavaScript. Among those: 4 AWS Secret Access Keys, 2 live Stripe secret keys (sk_live_), and 22 GitHub tokens with source code access. And according to GitGuardian's State of Secrets Sprawl 2025 report, 70% of secrets leaked in 2022 are still valid today — meaning most people never rotate them after exposure.

Let's make sure you're not in that 0.45%.


What Is an Environment Variable?

Think of an environment variable as a sticky note you put on the server, not inside the code itself. Instead of writing your database password directly in your code:

// BAD — never do this
const db = connect("postgresql://user:MyRealPassword123@host/db");

...you write:

// GOOD — reference the variable by name
const db = connect(process.env.DATABASE_URL);

The actual value (postgresql://user:MyRealPassword123@host/db) lives in Vercel's environment settings, encrypted at rest, never in your Git repository. Your code just knows what to ask for, not what the secret actually is.

This matters because your codebase is often semi-public — shared with AI tools, pushed to GitHub, visible to collaborators. Your secrets should not be.


The Most Dangerous Prefix in Next.js: NEXT_PUBLIC_

Here is the single most important thing in this entire article:

Any environment variable that starts with NEXT_PUBLIC_ is baked into your JavaScript bundle and sent to every browser that loads your site.

This is intentional. It exists for values you want public — your site's URL, a Google Analytics ID, a publishable Stripe key. But when AI coding assistants generate code and suggest NEXT_PUBLIC_ to fix a "variable not found" error in a component, they sometimes apply it to secrets that should never leave the server.

# DANGEROUS — this key is now visible to every visitor
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_abc123

# SAFE — only accessible in server-side code
STRIPE_SECRET_KEY=sk_live_abc123

# Correct use of NEXT_PUBLIC_ — this is genuinely public
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xyz456

The rule of thumb: if the variable name has "secret," "private," "password," "token," or a key prefix like sk_ or rk_live_ — it should never have NEXT_PUBLIC_ in front of it.

Vibe App Scanner's security guide for Vercel calls this the "NEXT_PUBLIC_ prefix trap" and marks it as the single most critical issue in Vercel + Next.js deployments.


Setting Environment Variables in the Vercel Dashboard

Vercel gives you three environments to work with:

  • Production — your live site
  • Preview — every pull request or branch deploy gets its own URL here
  • Development — what vercel dev uses locally

Here's how to add a variable:

  1. Go to your Vercel Dashboard
  2. Select your project
  3. Click SettingsEnvironment Variables
  4. Enter the key name (e.g., STRIPE_SECRET_KEY)
  5. Enter the value
  6. Choose which environments it applies to
  7. For truly sensitive values, toggle Sensitive — this stores the value in a non-readable format so even Vercel team members cannot see it after creation
  8. Click Save, then redeploy

A critical mistake many vibe-coders make: using the same secret keys across all three environments. As Vibe App Scanner notes, "anyone with a preview URL can trigger actions against production databases and services" if your preview environment shares production credentials. Create separate, limited-privilege API keys for preview and development.


How .env.local Works (and Why .gitignore Is Non-Negotiable)

When developing locally, you store variables in a file called .env.local at the root of your project:

# .env.local
STRIPE_SECRET_KEY=sk_test_your_test_key_here
DATABASE_URL=postgresql://localhost:5432/myapp_dev
OPENAI_API_KEY=sk-your-openai-key

Next.js automatically reads this file during local development. The critical rule: this file must be in your .gitignore.

Open your .gitignore file right now and confirm this line exists:

# local env files
.env*.local

If it's not there, add it before your next commit. A .env.local file committed to a public GitHub repo is one of the most common sources of leaked credentials. GitGuardian found 23.8 million secrets leaked on public GitHub in 2024 alone — a 25% increase from the year before.

A .env.example file (with fake placeholder values, no real secrets) is a great idea — it shows collaborators what variables they need to configure without exposing any real values:

# .env.example — safe to commit, no real values
STRIPE_SECRET_KEY=sk_test_replace_me
DATABASE_URL=postgresql://localhost:5432/myapp
OPENAI_API_KEY=sk-replace_me

The Principle of Least Exposure

Only expose to the frontend what the frontend actually needs.

Your React components, which run in the browser, need things like:

  • Your site's base URL (NEXT_PUBLIC_SITE_URL)
  • A publishable API key for a payment processor (NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY)
  • A public analytics ID

Your server-side code (API routes, Server Components, Server Actions) handles everything sensitive:

  • Database connection strings
  • Secret API keys
  • Auth tokens
  • Admin credentials

If a piece of data ever reaches a React component as a prop or a context value, assume it can be seen in DevTools. Design your architecture so secrets never make that journey.


A Checklist of Common Mistakes

Before every production deployment, ask yourself:

  • Did I accidentally add NEXT_PUBLIC_ to any secret key?
  • Is .env.local listed in .gitignore?
  • Did I run git status to confirm no .env files are staged for commit?
  • Are my preview/development environments using test or limited keys, not production credentials?
  • Have I checked my build logs for any accidentally printed secrets?
  • Are any keys hardcoded directly in component files (even temporarily)?

To check your git history for accidentally committed secrets:

git log -p --all -S 'sk_'
git log -p --all -S 'SECRET'

If you find anything, the key is already compromised — rotate it immediately, don't just remove it from history.


Going Further: Doppler and Secrets Management Tools

Once your project grows beyond a handful of variables — or when you have a team — managing secrets manually across Vercel environments becomes error-prone. Tools like Doppler sit between your team and your deployment targets. Instead of each developer maintaining their own .env.local and hoping the Vercel dashboard stays in sync, Doppler becomes the single source of truth.

Doppler integrates directly with Vercel and can automatically sync your secrets to the right environments. It also provides an audit log, so you can see who changed what and when — invaluable if you ever need to investigate a breach.

For smaller projects, Vercel's built-in environment variable management with the Sensitive flag enabled is sufficient. But it's worth knowing Doppler (and alternatives like HashiCorp Vault or AWS Secrets Manager) exist when you outgrow the dashboard.


Action Items Checklist

  • Audit NEXT_PUBLIC_ variables — search your codebase for any that contain "secret," "sk_," "token," or "password"
  • Check .gitignore — confirm .env*.local is excluded before your next commit
  • Separate your keys by environment — create test/staging API keys for Preview, reserve production keys for Production only
  • Enable Sensitive Variables in Vercel dashboard for all secret keys
  • Add a .env.example file — document required variables without exposing real values
  • Scan your git history using git log -p --all -S 'sk_' for any past leaks
  • Rotate any key you're unsure about — if there's any chance it was exposed, treat it as compromised
  • Never pass secrets from Server Components to Client Components as props
  • Use scoped preview keys with minimal permissions for all non-production deployments

Ask The Guild

Community Prompt: Have you ever accidentally exposed a secret key — or come close to it? What was the situation, and what did you do to fix it? Share your story (anonymously if you prefer) in the guild channel. Your experience might save someone else from a Monday morning surprise AWS bill. 🔐


Tom Hundley is a software architect with 25 years of experience. He writes the Security First series to help vibe-coders build confidently — and safely.

Copy A Prompt Next

Start safely

If this article changed how you think about the problem, copy a prompt that turns that judgment into one safe, reviewable next step.

Matching public prompts

6

Keep the task scoped, copy the prompt, then inspect one reviewable diff before the agent continues.

Need the safest first move instead? Open the curated sample prompts before you browse the broader library.

Start Here — Build Safely With AIStart Here — Build Safely With AI

Choose a Tiny First Win

How to pick a first project that teaches the workflow without dragging you into complex product and engineering problems.

Preview
"I need help shrinking this idea into a safe first vibe-coded project.
The big idea is: [describe idea]
Reduce it to the smallest useful version by:
1. removing anything that requires auth, billing, production data, or complicated integrations
2. keeping only one user and one core job to be done
Security First

Turn this security lesson into a repeatable review habit

This article gives you the judgment call. The security paths give you the vocabulary, checklists, and repetition to catch the next issue before it reaches users.

Best Next Path

Security Essentials

Guild Member · $29/mo

Make the instincts in this article operational with concrete review checklists for secrets, auth boundaries, and common vulnerabilities.

28 lessonsIncluded with the full Guild Member library

Need the free route first?

Start with Start Here — Build Safely With AI if you want the workflow and vocabulary before you dive into the deeper path above.

T

About Tom Hundley

Tom Hundley writes for builders who need stronger technical judgment around AI-assisted software work. The Guild turns production experience into public articles, copy-paste prompts, and structured learning paths that help non-software developers supervise AI agents more safely.

Do this next

Leave this article with one concrete move. Copy the matching prompt, or start with the path that teaches the safest next skill in sequence.