VibeRescue · Blog

Environment variables missing in production: a checklist for vibe coders

October 15, 2026 · 8 min read

"It works on my machine" is often an environment variable problem. Locally you have a .env file. In the Lovable, Bolt or Replit preview, the platform injects the values for you. On Vercel, Netlify or your own server, there's nothing until you add it. The symptoms vary: a blank page, undefined in API URLs, supabaseUrl is required, a Stripe or OpenAI call returning 401, or a build that fails with Missing required environment variable.

This checklist works for any stack an AI tool is likely to generate: Vite/React (Lovable, Bolt), Next.js (v0, Cursor) and Node backends (Replit). Go through it from top to bottom.

1. Make the full list of what your app needs

Don't trust your memory, or the AI's. Search the code:

# Vite / React
grep -rhoE "import\.meta\.env\.[A-Z0-9_]+" src | sort -u
# Next.js / Node
grep -rhoE "process\.env\.[A-Z0-9_]+" --include=*.{ts,tsx,js,jsx,mjs} . \
  --exclude-dir=node_modules | sort -u

Compare the result with your local .env and with what's set on the host. Write it down in a committed .env.example with placeholder values, never real ones:

# .env.example: names only, safe to commit
VITE_SUPABASE_URL=
VITE_SUPABASE_ANON_KEY=
STRIPE_SECRET_KEY=
OPENAI_API_KEY=

This file is the contract. Anyone (or any AI tool) setting up the project knows exactly what has to exist.

2. Get the prefix right: browser vs server

Frameworks deliberately hide variables from the browser unless you opt in with a prefix:

The two classic mistakes go in opposite directions. First, a client component reads process.env.SUPABASE_URL (no prefix) and gets undefined. Second, a secret gets renamed to NEXT_PUBLIC_OPENAI_API_KEY to "make it work", and now your OpenAI key is in a public JavaScript file anyone can download.

Rule of thumb: if a key can spend money or bypass security (OpenAI, Stripe secret, Supabase service_role, Resend, database URL), it must not have a public prefix. Call it from a server route or edge function instead.

3. Remember public variables are baked in at build time

Vite and Next.js replace VITE_* / NEXT_PUBLIC_* references with literal values while they build. That has two consequences:

# Dockerfile (Vite app)
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_ANON_KEY
ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY
RUN npm run build

Server-only variables (no prefix) are read at runtime, so they need to exist where the function runs. A redeploy still makes sure new values are picked up.

4. Set them for the right environment

Vercel has Production, Preview and Development scopes. Netlify has deploy contexts. Setting a variable for Preview only is a common reason "the preview link works but the real domain doesn't", and vice versa. Vercel's CLI shows the differences quickly:

vercel env ls production
vercel env ls preview
vercel env pull .env.production.local --environment=production   # inspect what prod gets

Also check where the build runs from. In a monorepo, if Vercel's Root Directory is apps/web, a .env at the repo root isn't read. Next.js and Vite look for .env files in the project root, not inside src/.

5. Watch for copy-paste damage

Values that are "set" but wrong cause the most confusing errors:

6. Fail loudly, at startup

Missing variables should break the build with a clear message, not show up later as a white screen. A tiny validation module does this. With zod, which many AI-generated projects already have:

// src/env.ts (Vite)
import { z } from "zod";
const schema = z.object({
  VITE_SUPABASE_URL: z.string().url(),
  VITE_SUPABASE_ANON_KEY: z.string().min(20),
});
export const env = schema.parse(import.meta.env);
// env.server.ts (Next.js, server only)
import "server-only";
import { z } from "zod";
export const env = z.object({
  DATABASE_URL: z.string().url(),
  STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
}).parse(process.env);

Import env instead of reading import.meta.env / process.env directly. If something is missing, the error names the exact variable.

7. Don't forget the secrets that live elsewhere

Not every secret belongs to your host:

8. If a secret leaked, rotate it

If .env was ever committed to a public repo, or a secret key had a public prefix, assume it's compromised. Deleting the file isn't enough, because it stays in git history and gets scraped quickly. Rotate the key in the provider's dashboard, update the host, and then add .env* (except .env.example) to .gitignore:

# .gitignore
.env
.env.*
!.env.example

Check with git log --all --full-history -- .env. If that prints anything and the repo is public, rotate.

The one-page checklist

If the variables are all correct and it still fails, the issue is probably redirect URLs or routing. See works in preview, breaks after deploy.

Platform-specific notes

Lovable / Bolt (Vite + Supabase). Preview injects keys. After you connect GitHub and deploy elsewhere, you must set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY on the host yourself. Some projects hard-code the keys in src/integrations/supabase/client.ts; those don't need env vars for Supabase, but any other API still does.

v0 / Cursor (Next.js on Vercel). Prefer putting secrets in Server Actions and Route Handlers. Client components should only see NEXT_PUBLIC_ values. Vercel's "Automatically expose System Environment Variables" option is separate from your own keys; turning it on does not add your Supabase or Stripe secrets.

Replit. Workspace Secrets are not always the same as Deployment Secrets. After you publish, open the deployment settings and confirm each key is present there too.

Self-hosted / cPanel / Docker. For a static Vite build, values must exist at build time. For a Node server, they must exist in the process environment at runtime (export in the start script, a systemd unit, or your host's env UI).

How to ask an AI tool to help without leaking secrets

Never paste real keys into a chat. Paste the names and the error:

On Vercel Production, the live site console shows:
  Uncaught Error: supabaseUrl is required
Code reads import.meta.env.VITE_SUPABASE_URL.
.env.example lists VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY.
Do not invent new variable names. Tell me exactly what to set where,
and whether I need to redeploy.

If the tool suggests renaming a secret to a public prefix, refuse. That's how keys end up in the browser bundle.

FAQ

Why is import.meta.env undefined in my production Vite build?

Vite only exposes variables prefixed with VITE_, and it replaces them at build time. If the variable was missing or misnamed when the build ran, it is undefined in the bundle. Set it on the host and redeploy.

Do I need to redeploy after changing an environment variable on Vercel?

Yes. Public variables (VITE_, NEXT_PUBLIC_) are inlined at build time, and new values apply only to new deployments. If a cached build keeps the old value, redeploy without the build cache.

Can I prefix my OpenAI or Stripe secret key with NEXT_PUBLIC_?

No. Public prefixes put the value into JavaScript that anyone can download. Keep secrets server-side and call them from a Route Handler, Server Action or edge function.

I committed my .env file to GitHub. What should I do?

Rotate every key in it right away, because it remains in git history even after you delete the file. Then add .env to .gitignore and commit only a .env.example with names, not values.

Not sure which of these it is?

Run the free Rescue Scan: paste a public GitHub repo and VibeRescue, an automated AI repair tool, classifies the problem (code bug, missing files, or missing secrets) before you pay anything. If it is something you can fix with this guide, we will tell you. If you want a fix run, it opens a pull request, with a full refund if the run can't fix it.

Run the free Rescue Scan →