Your environment variables are undefined in production
The deployed app crashes on load, and the console points at configuration:
Uncaught Error: supabaseUrl is required.
TypeError: Failed to construct 'URL': Invalid URL
Uncaught TypeError: Cannot read properties of undefined (reading 'VITE_API_URL')
Locally, everything works. The variable is set — you can see it in your
.env file. And yet production says undefined.
The one fact that explains all of it
Frontend environment variables are not read when the app runs. They are pasted into the code when the app is built.
When the build runs, every import.meta.env.VITE_API_URL in your source is
replaced with the literal string the build machine had at that moment. The
deployed site is static files; there is no server reading variables when a
visitor arrives. Three consequences, each of which is one of the classic
failures:
- Your local
.envnever travels. It is ignored by git (correctly), so the host's build machine has never seen it. The values must be entered in the host's own settings. - Setting a variable without rebuilding changes nothing. The old value —
or the
undefined— is already baked into the deployed JavaScript. Every change to variables needs a new deploy after it. - The prefix is a gate, not a convention. Vite only exposes variables
starting with
VITE_, Next.js withNEXT_PUBLIC_, Create React App withREACT_APP_. A variable namedAPI_URLis invisible to the frontend build on purpose — the prefix is you saying "this may be public".
Fix it on your host
The steps are the same everywhere; only the menu differs.
Netlify — Site configuration → Environment variables → add each variable → Deploys → Trigger deploy.
Vercel — Settings → Environment Variables → add for the environments that need it (Production at least) → redeploy from the Deployments tab.
Lovable / Bolt — the platform manages variables with the integration; the usual failure is a remix or duplicate of a project, where code arrives but values do not. Re-connect the integration (Supabase above all) in the copy, then publish again. More in the Bolt deploy guide.
Then verify the names character by character against what the code reads.
VITE_SUPABASE_ANON_KEY and VITE_SUPABASE_KEY are different variables, and
an AI that generated one name in code and another in documentation has done
exactly this to a lot of people.
The security line, because this is where it gets crossed
The VITE_/NEXT_PUBLIC_ prefix means public. Anything with it ships to
every visitor's browser, readable in the bundle.
- Fine to expose: the Supabase URL and anon key (designed for it — row-level security is what protects the data), a public analytics id, your API's public base URL.
- Never expose: the Supabase service-role key, OpenAI / Anthropic /
Stripe secret keys, database passwords, anything named
*_SECRET. If the frontend needs what those unlock, the call belongs on a backend or an edge function that holds the key server-side. An assistant asked to "make the API call work" will sometimes reach for the secret key with the public prefix — this is the thing to catch in review.
Hand it to the AI
Paste this to the AI that built your app
My deployed app fails with an environment-variable problem. The console error is: [paste the exact error] List every environment variable this codebase reads (search for import.meta.env, process.env.NEXT_PUBLIC, process.env.REACT_APP). For each: its exact name, whether it is safe to be public, and the value's source. Tell me which are missing for production on [Netlify/Vercel/Lovable/Bolt] and the exact steps to set them there. If any secret key (service role, sk_...) is read by frontend code, flag it and move that call server-side instead.
Prove it
After setting the variables and redeploying, open the site in a private
window with the console open: the startup errors must be gone. If requests
now reach your backend but come back 401/403, the variables are fine and
you have moved on to a permissions problem — likely
RLS.
Questions people also ask
I set the variable on Netlify/Vercel and it is still undefined. Why?
Frontend variables are inlined into the JavaScript when the site is built. A value added after the last build is not in the deployed code — trigger a new deploy and it will be.
Why does the variable work locally but not deployed?
Locally it comes from your .env file, which is (rightly) not committed and never uploaded. The host builds from your repository, where that file does not exist, so the host needs the values entered in its own settings.
Is it safe that my Supabase anon key is visible in the built code?
Yes — the anon key is designed to be public, and your data is protected by row-level security policies, not by hiding the key. What must never be in frontend code is a service-role key, an OpenAI/Stripe secret key, or any credential whose leak grants real power.