There is a particular kind of frustration that comes from deploying an application that worked perfectly on localhost and watching it fail in production.
The interface loads. The buttons are visible. Then authentication stops working, an API request returns an error or the application behaves as if an important service does not exist.
My first instinct used to be that the hosting platform had broken something. Sometimes the problem was much simpler: the deployed application did not have the same environment configuration as my local machine.
An environment variable is only a value made available to a specific application in a specific environment. Creating it in a local .env file does not automatically create it on the production server.
That sounds obvious when written down. During deployment, it is easy to forget.
I check whether the variable exists in the correct environment
Most hosting platforms separate production, preview and development settings. A variable added to one environment may not exist in another.
I begin by confirming three things:
the key exists in the hosting dashboard;
it is enabled for the environment I actually deployed;
the deployment happened after the value was added or changed.
Some platforms inject environment variables during the build. If I add a variable after the build has completed, restarting a page may not be enough. I may need a new deployment so the application is rebuilt with the correct configuration.
This is why I do not only ask, “Did I add the key?” I ask, “Did this exact deployment receive the key?”
The variable name must match exactly
Environment-variable names are case-sensitive in many systems.
SUPABASE_URL, Supabase_URL and SUPABASE_Url should be treated as different names. A missing underscore or an extra space can make the application read undefined even though the value appears to exist in the dashboard.
I compare the name in three places:
where the application reads it;
where it is defined locally;
where it is configured for production.
I copy the key name rather than retyping it from memory.
Browser variables and server variables are not the same
One of the most important questions is where the code runs.
Server code can read private credentials because those values stay inside the trusted environment. Browser code is delivered to the visitor. If a build tool exposes a variable to frontend code, the value can usually be inspected by anyone using the site.
Frameworks often use a naming convention to decide which variables may enter the browser bundle. That prefix is not a security feature that makes a secret safe. It is a signal that the value is public.
Public configuration can include something designed to be used in the browser, such as a public project URL or an anonymous client key with proper database policies. Secret service keys, private API credentials and payment-provider secret keys belong on the server.
I never solve a frontend configuration problem by exposing a secret variable to the browser.
I confirm that the value is complete
A key can exist and still be wrong.
It may contain quotation marks copied from an example. A URL may have a trailing space. A multiline private key may have lost its line breaks. A value may belong to a test project while the application is querying the production database.
I check the source of the value and copy it again from the official service dashboard when necessary. I also confirm that related variables belong to the same project and environment.
A production URL paired with a development key can produce behaviour that looks random but is actually consistent with mismatched configuration.
I log presence, not secrets
During diagnosis, it helps to know whether the application can see a variable. It is dangerous to print the entire value.
Instead of logging a secret, I log a safe condition such as whether it exists:
console.log('API key configured:', Boolean(process.env.API_SECRET_KEY))For a public URL, I may safely inspect the hostname. For a secret key, I avoid logging the value, even temporarily, because deployment logs may be stored or visible to other people.
If a secret has already appeared in public code, a repository, screenshot or log, deleting the text is not enough. I rotate the credential at the provider and update the application with the replacement.
I separate build-time and runtime behaviour
Some variables are read when the project builds. Others are read while the server is running.
This difference matters. A static frontend may bake public configuration into its JavaScript during the build. Changing the hosting setting will not alter the already-generated file. A serverless function may read its variable when it starts and therefore behave differently after a restart or redeployment.
When I know when the value is read, I know what must be rebuilt.
I inspect the real failing request
Environment problems often appear as vague frontend symptoms. A sign-in button seems unresponsive, but the network request is going to an undefined URL. A form says “Something went wrong,” but the server function could not find its private key.
I open the browser Network panel and inspect the failing request. Then I check the server or function logs. This follows the same layered approach I described in Authentication Bugs That Look Like Frontend Problems: I locate the layer where the expected state stopped being true.
My deployment configuration checklist
Before blaming production, I check:
Does the exact variable name match the code?
Is it configured for production rather than only preview or development?
Was the application redeployed after the value changed?
Does the value belong to the correct service project?
Is the code running in the browser or on the server?
Is any secret accidentally being exposed to frontend code?
Is the variable read at build time or runtime?
Does the failing network request contain the correct URL?
Do server logs show that the configuration is missing?
Does the same problem happen in a clean browser session?
“It works on my machine” does not mean production is unpredictable. It usually means the two environments are not as identical as I assumed.
Deployment becomes easier when configuration is treated as part of the application rather than as a final box to fill after the code is finished. It is one more example of why, as I wrote in Building Digital Products for Nigerian Users, a successful deployment is not the same thing as a product working correctly for the person holding the phone.
