“It works on my machine” — and then you deploy it.
The app is running. Auth works. Data loads. You click around for ten minutes, everything feels good, and you push it to a test server feeling pretty confident about yourself.
Then someone opens the link and cookies aren't being set. The frontend is redirecting to localhost:5173. The “today's events” section is showing tomorrow's data. And your API calls are getting blocked by CORS even though you literally have CORS set up.
Nothing in your code changed. But your environment did — and your code had no idea it was always depending on the environment staying the same.
These are the things that only show up after you leave localhost. Every single one of them I've either hit myself or watched someone else lose hours to.
1. Your cookies were never going to survive deployment
Locally, your frontend and backend are both on the same machine. The browser doesn't care much about cookie restrictions when everything's on localhost. So your auth works perfectly — cookies get set, requests carry them, life is good.
Then you deploy. Your frontend is on one domain, your backend is on another. Suddenly sameSite: 'strict' is blocking cookies from being sent cross-site. secure: trueis killing them if you're not on HTTPS yet. And you get zero error messages — the cookie just quietly doesn't arrive, and your entire auth flow breaks silently.
The fix is to make your cookie config aware of which environment it's running in. Don't hardcode these values:
Then use COOKIE_OPTIONS everywhere you set a cookie instead of writing the options inline. One file, one place to update, works correctly in both environments.
NODE_ENV=productionis actually set on your deployment server. If it isn't, isProductionwill be false and you'll be running prod with local cookie settings — which is a security issue, not just a bug.2. You hardcoded localhost somewhere and you don't remember where
Somewhere in your codebase is a URL that has localhost:5173 or localhost:3000written directly in it. Maybe it's in an OAuth redirect. Maybe it's in a navigate()call. Maybe it's in a link you generate dynamically. Locally it works perfectly because that's where your app actually is. After deploy your users are getting sent to your laptop.
On the frontend — stop constructing absolute URLs manually. Use window.location.origin to always get the current domain, whatever it is:
On the backend — keep your frontend URL in .env and use it everywhere you redirect:
This sounds obvious until you're staring at a production redirect going to localhost and trying to figure out where the hell it's coming from.
3. Your server thinks it's in America
This one is less common but when it hits you, it's genuinely confusing. You have a feature that fetches data based on today's date — events, logs, daily summaries, whatever. Works perfectly local. After deploy, the results are off by hours. Sometimes you're getting yesterday's data, sometimes tomorrow's.
The reason is that most deployment platforms (Render, Railway, AWS, Vercel serverless) default to UTC or a US timezone. If your users are in IST, that's a 5 hour 30 minute difference. At 11pm IST your server thinks it's still the previous day.
Install luxonand always work in the user's timezone, converting to UTC only when you actually query the DB:
Intl.DateTimeFormat().resolvedOptions().timeZone. Hardcoding 'Asia/Kolkata'works if your entire user base is in one region, but if you ever go wider, you'll want it dynamic.4. CORS that works locally but breaks the moment you deploy
You have CORS set up. You know you do. But after deploy every API call from the frontend is getting blocked. Postman works fine — it's just the browser that's angry.
Two reasons this usually happens. First — your CORS origin is still set to localhost:5173 and your frontend is now on a real domain. Second — you have credentials: true on the backend but forgot withCredentials: true on the frontend, so cookies are never actually sent with requests.
credentials: true on CORS and withCredentials: trueon Axios both have to be set. One without the other and cookies won't travel. This is the most common auth bug after deployment and it looks completely unrelated to cookies when you first see it.5. You added a new .env key and forgot to add it on the server
You built a feature locally, added a new environment variable, everything works. You push the code. The feature throws a 500 in prod. You check your code for twenty minutes before realizing process.env.SOME_NEW_KEY is just undefined on the server because you never added it there.
The error has nothing to do with the actual problem. It usually looks like a null reference or a failed API call — not “hey your env variable is missing.”
Simple habit — keep an .env.example file in your repo. Every time you add a key to .env, add it here too with an empty value:
When you deploy, this file is your checklist. Go through it line by line and make sure every key exists on your prod server. Takes thirty seconds and saves you from that specific brand of confusion where everything looks fine but nothing works.
6. Test your prod build before you actually push to prod
The dev server hides a lot. Vite's hot reload doesn't care about broken imports, missing env variables that get baked in at build time, or components that only fail during compilation. You'll find all of this the hard way if you push straight to prod without building first.
Before pushing, run this in your frontend folder:
Then temporarily add http://localhost:4173 to your backend CORS, point your frontend API URL to your local backend, and actually use the app on port 4173. This is as close to prod as you can get without actually deploying.
localhost:4173from CORS before you actually deploy. It's a testing tool, not something that should be in your production config.7. MongoDB Atlas is silently rejecting your server
This one catches everyone at least once. Your app deploys, the server starts, and every DB query just hangs and eventually times out. Locally your DB connection is fine. In prod it looks like a connection issue but the error message is vague enough that you'll probably check your connection string three times before figuring it out.
Atlas has an IP whitelist. By default it's locked down. Your local IP is probably added from when you set it up. Your deployment server's IP is not.
Go to Atlas → Network Access → Add IP Address. Either add your server's specific IP or — if your deployment IP changes (like on Render free tier) — allow access from anywhere (0.0.0.0/0) and rely on your connection string credentials for security.
0.0.0.0/0 is the practical choice. Just make sure your MongoDB username and password are strong and not reused anywhere.None of these are code bugs. That's what makes them annoying. Your logic is correct, your feature works, your tests pass — and then the environment changes and everything falls apart.
The pattern across all of these is the same: your local setup was covering for assumptions your code was making. Cookies on the same origin. URLs that always start with localhost. A server that's in your timezone. A DB that trusts your IP.
Write code that doesn't assume where it's running. Use environment variables. Build before you push. And maybe keep this list open the next time something works perfectly on your machine.