Works on my laptop. Dead on the server. No error. The single most common way the machine lies to you.
The app ran perfectly on the laptop. Moved it to the droplet to run on a schedule. It just… stopped. No crash. No error in the obvious place. Nothing.
"It worked on my laptop. On the droplet — dead. I was sure the keys were fine; I'd set them. So why did it work here and not there?"
"You had set them — in the laptop's .env, which is gitignored and never travels with the code. I should've asked where faster. The keys didn't carry. That's the whole lesson."
Two kinds, specifically. Secrets — your API keys — because if they're in the code they go into git and leak the moment it's shared. And things that change by place — the database is different on your laptop vs. production. Hard-code either and you're stuck choosing between leaking secrets or editing code every time you move.
named, outside the codeA value the code asks for by name at runtime. The environment hands it over — it never lives in the code.
gitignored, per-machineHolds the values locally. Never committed. So it does not travel with the code to a new machine.
set them freshLaptop, droplet, Vercel — each needs its own copy. That's the feature, not the bug.
The model: your code is a recipe that says "add salt from the jar labeled SALT." It never hard-codes the amount — it reads the jar. Different kitchens stock the jar differently. That's why the same code runs safely everywhere: the values aren't inside it.
It runs on the laptop, fails on the droplet, no real error. You're certain you set the keys. What happened?
.env is gitignored, so when the code lands on a new machine the code arrives but the secrets don't. The droplet ran with empty keys → 401. Every new environment needs its own copy. (And nastier cousin: a value set with a hidden trailing newline reads as "true\n" ≠ "true" — silently false, prod only.).env locally. Confirm it's in .gitignore — never commit it..env didn't travel; you bring it.NEXT_PUBLIC_ bakes a value into the browser bundle — never put a secret behind it.Same rule as always: answer it right, or the module isn't done.
Safe ≠ local. Safe = not in the code. The environment hands the key to the code wherever it runs — so every new place needs its own.
A real check-env.js that fails loud before your app starts if a required variable is missing — or has a hidden trailing newline (the exact bug that bit me three times in one day). Plus a new-machine checklist so the keys never silently fail to carry again.