Environment variables are undefined in production (Vite and Next.js apps from Cursor, v0 and Bolt)
Updated 2026-11-11
What you see
It works locally with your .env file. In production the page is blank or the console shows something like:
Error: supabaseUrl is required.
or your code reads undefined where a key or API URL should be.
Why it happens
There are three usual causes.
1. The .env file never reached the host. It is (correctly) in .gitignore, so Vercel, Netlify or Cloudflare never see it. You have to enter the same variables in the host’s dashboard.
2. The variable has no public prefix. Browser code only gets variables that are explicitly marked public:
- Vite (Bolt, Lovable and most Cursor-built React apps): only variables prefixed with
VITE_are exposed, read throughimport.meta.env.VITE_…. Others come backundefined. - Next.js (v0 and many Cursor apps): only
NEXT_PUBLIC_…variables are inlined into browser JavaScript. Variables without the prefix exist only on the server.
3. You changed the value but didn’t rebuild. Both Vite and Next.js replace these variables with their literal values at build time. Next.js’s docs warn that after the build, the app “will no longer respond to changes to these environment variables.” Editing a value in the dashboard does nothing until you redeploy.
Fix it
- List every variable your browser code reads: search the code for
import.meta.envorprocess.env.NEXT_PUBLIC_. - Add each one, with its exact name, in your host’s environment settings for the Production environment (and Preview if you use it).
- Check the prefix matches your framework (
VITE_orNEXT_PUBLIC_). - Trigger a new deployment so the build picks up the values.
Next.js also supports dynamic lookups like process.env[name], but they are not inlined in the browser. Reference each variable by its full literal name.
The security part (read this)
Anything with a VITE_ or NEXT_PUBLIC_ prefix is pasted into your public JavaScript bundle, where anyone can read it. Vite’s docs say plainly that VITE_* variables “should not contain sensitive information such as API keys.”
- OK to expose: your Supabase project URL and publishable/anon key (when RLS is on), analytics IDs, public Stripe publishable keys.
- Never expose: Supabase
service_role/secret keys, Stripe secret keys, OpenAI/Anthropic keys, database passwords. Use these only in server code (API routes, serverless or edge functions).
If a secret was ever bundled or committed, rotate it at the provider. Run a secret scanner over the repo history before you make it public.
Check it worked
After redeploying, open DevTools → Network and confirm requests go to the right API URL. Then search the built JS (Sources tab) for the first few characters of your secret keys: they must not be there.