A debugging story about Next.js, NEXT_PUBLIC_ variables, and why I now reason about bugs from first principles.
The symptom
I was building a Custom Words feature for my pass-the-phone party game, a Next.js and Capacitor PWA. Everything looked right in code review, but the behavior was wrong. Words saved locally didn't match what the UI showed, hints looked corrupted, and fixing the logic changed nothing.
I spent hours reading my own code. The code was fine.
The first-principles reset
I stopped asking "what's wrong with my logic?" and asked a more basic question: where is this request actually going?
Every web feature reduces to the same chain: the UI triggers a call, the call goes to a URL, something answers, and the answer is rendered. I had verified the first and last links but never the middle one.
I opened the Network tab, and the request wasn't going to localhost:3000. It was going to my deployed Vercel app.
The root cause
My .env.local contained this:
NEXT_PUBLIC_API_BASE_URL=https://my-app.vercel.app
I had added it for the Capacitor mobile build, where the app is a static bundle with no server of its own and needs an absolute URL to reach the backend. It leaked into my web dev environment.
Two rules explain why this was so effective at fooling me:
-
NEXT_PUBLIC_*variables are inlined into the client bundle at build time. They aren't read at runtime. The value is baked into your JavaScript. -
An absolute URL bypasses your local server entirely. Relative paths like
/api/wordshit whatever server is serving the page. A fullhttps://...URL hits that host, no matter where you are.
So my local frontend was talking to production. The data, schema, and responses all came from a different environment than the code I was editing, which is why the behavior looked like a logic bug.
Why it stayed hidden
A service worker made it worse. It was caching responses, so even after I started suspecting the environment, stale cached data kept showing up. And since the bad responses had already been written into IndexedDB (via Dexie), the corruption persisted across reloads.
Three layers (environment, cache, local persistence) were each hiding the others.
The fix
I didn't just delete the variable. I made the failure mode impossible to repeat:
-
Env separation:
NEXT_PUBLIC_API_BASE_URLnow lives only in.env.mobile(gitignored), with a committed.env.mobile.exampledocumenting it. It must be absent from.env.local. - A mismatch guard: the app now detects when the configured API host doesn't match the environment and fails loudly instead of silently talking to the wrong server.
- A DB migration: a Dexie schema bump (v7) cleared the corrupted data already sitting in IndexedDB.
- Service worker off in dev: it's registered only in production builds, so caching can't mask environment problems.
// Guard sketch: fail loudly on a config mismatch
const base = process.env.NEXT_PUBLIC_API_BASE_URL;
if (process.env.NODE_ENV === 'development' && base) {
throw new Error(
'NEXT_PUBLIC_API_BASE_URL is set in dev. It is mobile-only; remove it from .env.local.'
);
}
The lessons
- Verify the path before the logic. Check where the request goes before debugging what it does.
- A config that serves two targets will eventually serve the wrong one. Web and mobile have different needs, so give them separate files.
- Silent fallbacks are bugs waiting to happen. A loud failure costs minutes, while a quiet misroute cost me hours.
- Caches and persisted state turn a fixed bug into a haunted one. After fixing a root cause, ask what's still holding the old bad data.
-
NEXT_PUBLIC_means public and baked in. Treat it as a build-time constant, not a runtime setting.
Takeaway
Debugging works best when you reduce the problem to its smallest true statement. Mine was "this request isn't going where I think it is." That sentence took five minutes to confirm and ended hours of looking in the wrong place.
What's the worst environment bug you've lost hours to? Tell me in the comments.
I'm Syed Ahmed Ali, a full-stack developer who debugs from first principles and builds guardrails so the same bug can't come back. If you need that on your team or project, you can find me at syedahmedali.com.
Top comments (0)