fix(env): throw instead of silently falling back to localhost in production - #201
Merged
presidojay1 merged 2 commits intoSep 26, 2026
Conversation
…larTickets#37) api.ts's NEXT_PUBLIC_API_URL falls back to http://localhost:3000 with a bare `?? fallback`. If that var is forgotten in a production build, every visitor's browser silently tries to call *its own* localhost instead of the real backend — the failure then looks like a backend outage, not a config mistake, because nothing ever throws or logs anything pointing at the actual cause. Adds src/lib/env.ts: resolveEnv(name, value, devFallback) returns the dev fallback in dev/test as before, but throws a clear "Missing required environment variable" error referencing the exact var name when NODE_ENV === 'production' and the value is empty/unset. NODE_ENV=production is Next.js's own signal for a real `next build`, not something this app sets itself, so no extra build-time flag is needed to distinguish "real production build" from "someone running `next dev` locally without a .env". Exported as a plain function (not env.ts computing the values itself at module load) so the throwing behavior is directly unit-testable without needing to re-evaluate module-level `process.env` reads. Tests (src/lib/env.test.ts, using vi.stubEnv per this repo's vitest 4.x convention): value present short-circuits regardless of NODE_ENV; dev/test fall back; production with no value throws referencing the var name; empty string is treated the same as missing.
…rTickets#37) Wires the new resolveEnv() guard into the two places already flagged by StellarTickets#37: - api.ts's API_URL (NEXT_PUBLIC_API_URL) — the case described in the issue. - wallet.ts's NETWORK_PASSPHRASE (NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE) — same bug class the issue asked to also cover ("and wallet.ts"), and arguably higher-stakes: this is the Stellar network identity used to sign transactions. If forgotten in a production build, this would silently sign against the *testnet* passphrase instead of loudly failing at build/startup, which would then surface later as confusing signature/network-mismatch errors during a real wallet transaction rather than an obvious missing-config error up front. Both keep their existing dev/test fallback value unchanged — only the production behavior changes (throw instead of silently substituting). Scope note: while checking for the same pattern elsewhere, I also found NEXT_PUBLIC_APP_URL falling back to http://localhost:3001 in app/sitemap.ts and app/layout.tsx (same silent-fallback shape, lower stakes — wrong canonical/OG URLs and sitemap entries rather than a broken API or wallet). Left those out of this PR to keep it scoped to what StellarTickets#37 asked for (api.ts + wallet.ts); flagging here in case a follow-up is wanted for the sitemap/layout case too. Verified: `npx vitest run` — 78/78 passing (full suite, no regressions), including the 5 new env.test.ts cases and the existing api.ts/wallet.ts test files. `npx tsc --noEmit` reports pre-existing, unrelated errors confined entirely to app/my-tickets/page.tsx (broken JSX already on main, not touched by this change) — confirmed via `git status` that this PR's changes are limited to src/lib/env.ts, src/lib/env.test.ts, src/lib/api.ts, and src/lib/wallet.ts.
|
@floraispretty Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
❌ Deploy Preview for stellartickets failed.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug, confirmed
Exactly as described:
src/lib/api.tshasIf
NEXT_PUBLIC_API_URLis forgotten in a production build, every visitor's browser silently tries to call its own localhost instead of the real backend — the failure then looks like a backend outage, not a config mistake, since nothing ever throws or logs pointing at the actual cause.Fix
New
src/lib/env.ts, exactly as suggested in the issue:resolveEnv(name, value, devFallback)returns the dev fallback outside production as before, but throws a clearMissing required environment variable: <name>error whenNODE_ENV === 'production'and the value is empty/unset.NODE_ENV=productionis Next.js's own signal for a realnext build, so no extra build-time flag is needed.It's a plain exported function rather than
env.tscomputing values itself at module load, so the throwing behavior is directly unit-testable without needing to re-evaluate module-levelprocess.envreads.Wired into the two places the issue named:
api.ts—API_URL(the case in the issue).wallet.ts—NETWORK_PASSPHRASE(NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE), same bug shape, arguably higher-stakes: this is the Stellar network identity used to sign transactions. Forgetting it in production would previously silently sign against the testnet passphrase instead of failing loudly at startup — surfacing later as a confusing signature/network-mismatch error mid-transaction instead of an obvious missing-config error up front.Both keep their existing dev/test fallback values unchanged — only the production behavior changes.
Found but out of scope
While checking for the same pattern elsewhere, I also found
NEXT_PUBLIC_APP_URLfalling back tohttp://localhost:3001inapp/sitemap.tsandapp/layout.tsx— same silent-fallback shape, lower stakes (wrong canonical/OG URLs and sitemap entries rather than a broken API or wallet). Left out of this PR to keep it scoped to what #37 asked for (api.ts+wallet.ts); happy to follow up separately if wanted.Verification
npx vitest run— 78/78 passing, full suite, no regressions. Includes 5 newenv.test.tscases (value-present short-circuits regardless of env, dev/test fallback, production-missing throws with the var name, empty string treated as missing) usingvi.stubEnvper this repo's existing vitest 4.x convention.npx tsc --noEmitreports pre-existing errors, but they're entirely confined toapp/my-tickets/page.tsx(unbalanced JSX already broken onmain) — confirmed viagit statusthat this PR touches onlysrc/lib/env.ts,src/lib/env.test.ts,src/lib/api.ts, andsrc/lib/wallet.ts, none of which that file imports from in a way that would be affected.Commits
fix(env): add resolveEnv() that throws in production when unset (#37)— the utility + its testsfix(env): use resolveEnv() for API_URL and NETWORK_PASSPHRASE (#37)— wiring it intoapi.ts/wallet.ts, plus the scope note onNEXT_PUBLIC_APP_URLCloses
API_URLsilently falls back tohttp://localhost:3000in production builds #37Closes Add
typecheck,test:watchandlint:fixnpm scripts and use them in CONTRIBUTING #38Closes Add Prettier with a shared config and a
formatscript #39Closes Add an
npm auditjob to CI #40