My Environment Variables Work Locally and Not in Production

Written By
SprintX Team
AI & Product Engineering
August 07, 2026
6 min read

The four reasons an environment variable is undefined in production but fine on your machine, how to tell them apart, and how to manage secrets properly.
Locally it reads the key and everything works. You deploy, and the same line of code gets undefined. The variable is set in the dashboard — you checked three times — and the app still does not see it.
Environment variables feel like they should be simple, and they are, right up until a build step gets involved. Then there are four separate places a value can go missing, and the error message never tells you which one.
Here is how to identify yours in a couple of minutes rather than an afternoon of redeploys.
Prove where the value disappears
Do not guess. Add a temporary log at the top of the code that reads the variable, printing the key name and whether the value is present — never the value itself, and never in code that runs in the browser.
Then look at where that log appears:
- In the build logs, showing undefined — the variable is not available at build time.
- In the runtime logs, showing undefined — it is not available at runtime, or not in that scope.
- Present in the server logs, undefined in the browser — this is correct behavior; keep reading.
- Present everywhere, but wrong value — you have two environments or two sources, and the wrong one is winning.
Those four outcomes map onto four distinct causes.
Cause 1: build time versus runtime
This is the one that costs people the most hours. Frontend frameworks read certain variables during the build and bake the literal value into the output bundle. Server-side code reads them when the process starts.
The consequences:
- Changing a build-time variable in your host's dashboard does nothing until you redeploy. The old value is already compiled into the JavaScript that is being served. Setting the variable and refreshing the page is not a test.
- A variable added after the build ran will be missing even though the dashboard shows it as set.
- Preview and production builds are separate builds, so a variable set for only one of them silently disappears in the other.
If you changed a value and nothing happened, redeploy before you debug anything else. That resolves this category outright.
Cause 2: the public prefix rule
Frameworks refuse to expose arbitrary variables to the browser, because that would leak every secret you have. They expose only names carrying a specific prefix — NEXT_PUBLIC_ in Next.js, VITE_ in Vite, REACT_APP_ in Create React App, PUBLIC_ in several others.
Two failure modes, and they are opposites:
- Missing prefix. Your client component reads a variable that has no prefix, so the framework strips it and you get undefined. Rename it with the prefix and redeploy.
- Prefix on a secret. Someone hit failure mode one, added the prefix to a service key or a Stripe secret to make the error go away, and shipped it. That value is now in the JavaScript bundle, readable by anyone. This is not theoretical — it is one of the most common ways AI-built apps leak credentials, and the pattern is covered in API keys exposed in the frontend.
The rule to hold: if the browser needs it, it is public information and the prefix is correct. If it is a secret, the browser must never call that service directly — route the call through your backend instead. Anything else is a leak with extra steps, and if you have already shipped one, what to do when a key is exposed is the recovery order.
Cause 3: scope and environment mismatch
Hosts split variables by environment, and it is easy to set one and not the others.
| Where it works | Where it fails | Usual reason |
|---|---|---|
| Preview deploys | Production | Variable added to preview scope only |
| Production | Preview and branches | Same, inverted |
| Web app | Serverless or edge functions | Functions have their own environment config |
| Main app | Background worker or cron | Separate service, separate variables |
| Local | Everywhere else | Value only exists in your untracked env file |
Supabase edge functions, Cloudflare Workers, and separately deployed workers all keep their own secrets. Setting a value on your web project does not propagate to them, and the resulting error looks identical to a missing variable in the main app.
Cause 4: the value is set but wrong
Less common, more frustrating. Watch for trailing whitespace or a newline pasted with the value; quotes wrapped around a value in a dashboard field that does not want them; a URL with a trailing slash producing double slashes; multiline values such as private keys needing escaped line breaks; and the classic of pasting a test key into production. That last one shows up as payments failing live while working in test — see Stripe working in test and failing live.
Also check load order. A local env file, a shell export, and a host-level variable can all define the same name, and precedence differs by framework. If you have two definitions, delete one.
Set them up so this stops happening
Once the immediate fix is in, spend twenty minutes on the structure. It pays back on the first deploy after.
- Keep an example file in the repo. Every key your app needs, with placeholder values and a comment on where to get each. New environments then have a checklist instead of a guessing game.
- Fail fast on startup. Validate that every required variable is present when the app boots and refuse to start if one is missing. A clear crash at deploy beats a mysterious undefined at 2 a.m. from a customer.
- Name by intent. A prefix separating public from secret, a suffix or name that makes environment obvious. Ambiguous names are how test keys reach production.
- Never commit real values. The scale of this problem is not small: GitGuardian counted 28.65 million new hardcoded secrets in public GitHub commits in 2025, up 34% year over year. Turn on push protection in your git host.
- Document who holds what. For a small team, a shared password manager entry per environment is enough, and it beats "ask whoever set up the deploy."
If the variables are only one symptom of a broader gap between your machine and your host, the wider checklist lives in an app that works locally and not in production, and the deployment mechanics for most generated frontends are in deploying an app to Vercel.
Frequently asked questions
Why does my variable work in preview but not production? Because most hosts scope variables per environment and the value was only added to preview. Check every scope your host offers, including branch deployments, and confirm the production build ran after you added it.
Can I read secrets in a client component if I am careful? No. Anything the browser receives is readable by anyone who opens DevTools — obfuscation and minification change nothing. If the browser needs data from a secret-protected service, proxy the call through your own backend.
Do I need a secrets manager? For a small team, your host's environment settings plus a password manager is genuinely fine. Move to a dedicated manager when you need rotation, per-service access control, or an audit trail — usually the moment a compliance review starts.
If a value that is plainly set in your dashboard still comes back undefined in production, the cause is one of four things and none of them require more redeploys to find. SprintX sorts out configuration and deployment for AI-built apps, including getting secrets out of bundles where they should never have been. Send us your repo and host details.


