Why my app works locally but breaks in production — 3 causes and the fix

A familiar sting: on your laptop everything clicks and flies, you deploy — and production is a blank screen or a dry 500 error. But the code is the same! That's the catch. The problem is almost never in the code — it's that your local environment and production are two different worlds. Let's cover the three causes 90% of beginners hit, and how to catch each.
Why this even happens
Locally, everything around your code is "yours": your files, your settings, your localhost address. On the server it's all foreign — a different machine, a different OS, none of your local crutches. The code is identical, but the environment around it is different. So we're not hunting a logic bug — we're hunting what existed on your machine and didn't make it to the server.
Cause 1: environment variables didn't make it
The most common — start here. Database keys, API tokens, secrets live in your .env file locally. And that file doesn't deploy — it's in .gitignore, and rightly so, or your secrets would leak. Result: locally the key exists, in production it's empty, and the app crashes on the first call to the database or API.
- How to check: open your host's dashboard (Vercel, Netlify, Railway) → Environment Variables. Empty, or half of them missing? There's your cause.
- How to fix: enter all the variables from your
.envthere by hand and redeploy. More in what environment variables are and why they aren't working.
Cause 2: localhost is hardcoded somewhere
While developing, the frontend often calls the backend at http://localhost:3000. Locally that works — both run on your machine. In production localhost means "the server itself," not your backend, and requests fly into the void. Same trap: a hardcoded port or path that doesn't exist in production.
- How to check: search the code for
localhostand for specific ports. Found it in request URLs? That's it. - How to fix: move the address into an environment variable. Locally —
localhost, in production — the real domain. Same code, different values per environment.
Cause 3: the file exists for you but not on the server
Sneaky and infuriating. It works like this: you created a file, the app uses it — but it never reached the server. Two typical scenarios:
-
Forgot to commit. The file is on your machine but not in git — everything shipped except it.
-
Case in the name. Your Mac or Windows doesn't distinguish
Header.jsfromheader.js, but a Linux server does. Locally the import finds the file, in production it doesn't, and the build collapses. -
How to check: run
git status— is the file in the untracked list? And in your imports, compare the letter case against the real file name. -
How to fix: commit what's missing; match the case in imports exactly to the file name. A tiny thing that kills production dead.
First move — roll back
While you dig for the cause, production is down and users see an error. Don't play hero live — first roll back to the last working version. That gives people a working site back in seconds, and then you can calmly, fire-free, walk through the three causes above. The order is always the same: stability first, investigation second — as with any failed deploy.
Q: How do I catch this before it happens, not after?
Keep your local environment close to production: the same language/platform version, real variable values in a test, deploys in small chunks. And above all — don't assume "works for me" equals "works for everyone." Those are two different checks. Every couple of deploys, open your production URL on your phone over mobile data, not just localhost on the laptop — that shows you exactly what the user sees.
Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.





