Environment variables undefined in production (Vercel, Netlify, Next.js, Vite)

By BuildUnstuck · · checked against Next.js, Vite, Vercel and Netlify docs

When a variable works on your laptop but is undefined live, it's almost always one of four things: the browser can't see it because it lacks the framework's public prefix (NEXT_PUBLIC_ in Next.js, VITE_ in Vite), it was never added to your host's Production environment, you changed it but didn't redeploy, or your code reads it in a way the build can't replace. Your .env file is not uploaded with your code, so the host only knows what you typed into its dashboard.

What this guide covers

Your AI-built app (Cursor, Lovable, Bolt, v0, Claude Code) runs locally. After you deploy, you see errors like "supabaseUrl is required", "Missing API key", a blank page, or a request going to undefined/api/.... Those all point to a value your code expected from an environment variable that isn't there in production.

Step 1: Make sure the variable exists on your host, for Production

Your local .env or .env.local file is usually git-ignored, which is good, but it means your host never sees it. Add each variable in the host's settings:

  • Vercel: Project Settings → Environment Variables. Each variable is set per environment (Production, Preview, Development). A value saved only for Preview won't exist on your live domain.
  • Netlify: Site configuration → Environment variables. Check the variable's scopes and deploy contexts so it applies to builds and to your production context.

Copy the exact name. SUPABASE_URL and NEXT_PUBLIC_SUPABASE_URL are different variables, and a trailing space in a name or value is a common silent failure.

Step 2: Browser code needs the public prefix

Frameworks hide environment variables from the browser by default so secrets don't leak. Only prefixed names are bundled into client code:

  • Next.js: variables starting with NEXT_PUBLIC_ are inlined into the JavaScript sent to the browser during next build. Without the prefix, a variable is only available on the server (API routes, server components, server actions).
  • Vite (used by Lovable, Bolt and many React starters): only variables starting with VITE_ are exposed, and you read them with import.meta.env.VITE_NAME, not process.env.

If a component in the browser reads a variable without the prefix, it gets undefined in production even if it seemed to work in some local setups.

Step 3: Redeploy after every change

Public variables are baked into the build, so the value is fixed when the build runs. Vercel's docs say changes to environment variables are not applied to previous deployments, only to new ones. After adding or editing a variable, trigger a fresh deploy (and on Vercel, avoid reusing an old build cache if the value still looks stale). The same rule holds on Netlify: change the variable, then deploy again.

Step 4: Read the variable the way the build expects

Build tools replace public variables by finding the literal name in your code. Write process.env.NEXT_PUBLIC_API_URL or import.meta.env.VITE_API_URL in full. Next.js notes that dynamic lookups such as process.env[name] or destructuring from process.env are not inlined, so they come back empty in the browser.

Step 5: Don't fix it by making a secret public

The quick "fix" an AI tool may suggest is renaming a secret key to NEXT_PUBLIC_ or VITE_. That ships the key to every visitor. Public prefixes are fine for values designed to be public (a Supabase anon/publishable key, a Stripe publishable key, your site URL). Secret keys (Stripe secret key, Supabase service_role, OpenAI or other AI provider keys) belong on the server: call them from an API route or server function instead.

Step 6: Check what actually shipped

  • Open the live site, then the browser console and Network tab. A request to undefined/... or a "required" error names the missing value.
  • Look at the host's build log for warnings about missing variables, and the function or runtime logs for server-side errors.
  • Temporarily log whether a value exists (for example Boolean(process.env.X)) on the server, never the value itself.

Quick answers

Why is my environment variable undefined in the browser but fine on the server?

Browser code only sees variables your framework deliberately exposes. In Next.js that means names starting with NEXT_PUBLIC_; in Vite it means names starting with VITE_, read through import.meta.env. Anything without the prefix stays server-only on purpose.

I changed an env var on Vercel. Why does the live site still use the old value?

Vercel applies environment variable changes only to new deployments, not to ones already built. Redeploy after you change a value, and check you set it for the Production environment, not just Preview or Development.

Is it safe to put my API key in a NEXT_PUBLIC_ or VITE_ variable?

Only if the key is meant to be public, like a Supabase anon/publishable key or a Stripe publishable key. Prefixed variables are bundled into JavaScript anyone can read, so secret keys (Stripe secret, Supabase service_role, OpenAI) must stay server-side.

Sources