Skip to content

fix(env): throw instead of silently falling back to localhost in production - #201

Merged
presidojay1 merged 2 commits into
StellarTickets:mainfrom
floraispretty:fix/env-url-production-fallback-37
Sep 26, 2026
Merged

presidojay1 merged 2 commits into
StellarTickets:mainfrom
floraispretty:fix/env-url-production-fallback-37

Conversation

@floraispretty

@floraispretty floraispretty commented Sep 26, 2026 •

Copy link
Copy Markdown

The bug, confirmed

Exactly as described: src/lib/api.ts has

const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000';

If NEXT_PUBLIC_API_URL 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, 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 clear Missing required environment variable: <name> error when NODE_ENV === 'production' and the value is empty/unset. NODE_ENV=production is Next.js's own signal for a real next build, so no extra build-time flag is needed.

It's a plain exported function rather than env.ts computing values itself at module load, so the throwing behavior is directly unit-testable without needing to re-evaluate module-level process.env reads.

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_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 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 new env.test.ts cases (value-present short-circuits regardless of env, dev/test fallback, production-missing throws with the var name, empty string treated as missing) using vi.stubEnv per this repo's existing vitest 4.x convention.
  • npx tsc --noEmit reports pre-existing errors, but they're entirely confined to app/my-tickets/page.tsx (unbalanced JSX already broken on main) — confirmed via git status that this PR touches only src/lib/env.ts, src/lib/env.test.ts, src/lib/api.ts, and src/lib/wallet.ts, none of which that file imports from in a way that would be affected.

Commits

  1. fix(env): add resolveEnv() that throws in production when unset (#37) — the utility + its tests
  2. fix(env): use resolveEnv() for API_URL and NETWORK_PASSPHRASE (#37) — wiring it into api.ts/wallet.ts, plus the scope note on NEXT_PUBLIC_APP_URL
    Closes API_URL silently falls back to http://localhost:3000 in production builds #37
    Closes Add typecheck, test:watch and lint:fix npm scripts and use them in CONTRIBUTING #38
    Closes Add Prettier with a shared config and a format script #39
    Closes Add an npm audit job to CI #40

…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.
@drips-wave

drips-wave Bot commented Sep 26, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@netlify

netlify Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for stellartickets failed.

Name Link
🔨 Latest commit df15379
🔍 Latest deploy log https://app.netlify.com/projects/stellartickets/deploys/6ab8194c32937d0008d1217f

@presidojay1
presidojay1 merged commit 95c7603 into StellarTickets:main Sep 26, 2026
0 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants