What's Happening
Environment configuration is undocumented, unvalidated, and in one case actively dangerous:
- A secret is templated as client-exposed.
.env.example includes NEXT_PUBLIC_CLOUDINARY_API_SECRET=your_cloudinary_api_secret (and NEXT_PUBLIC_CLOUDINARY_API_KEY). Anything prefixed NEXT_PUBLIC_ is inlined into the browser bundle — an API secret must never carry that prefix. The actual upload code (lib/utils/cloudinaryUpload.js) only uses NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME + an unsigned upload_preset, so the secret/key/url entries are unused and teach every contributor to paste a secret into a public-bundle variable.
- Used variables are missing from the template.
NEXT_PUBLIC_AI_API_URL is read in app/api/ai/stream/route.js, app/api/ai/chat/route.js, components/organisms/dashboard/ai/Ai-Sidebar.jsx, and lib/actions/ai/load-chat-history.js (silently defaulting to http://localhost:8000), but does not appear in .env.example. DNB_API_URL is listed but nothing reads it.
- No validation. A misconfigured deployment fails at runtime in scattered ways:
StellarProvider reads NEXT_PUBLIC_STELLAR_NETWORK with a silent "testnet" fallback, axios.config.js falls back to a hardcoded Render URL, firebase.config.js hardcodes the entire Firebase config in source instead of env vars.
- The README's configuration section documents only
NEXT_PUBLIC_API_URL.
Where to Find This
.env.example
lib/utils/cloudinaryUpload.js, hooks/useCloudinaryUpload.js — actual Cloudinary usage
app/api/ai/stream/route.js, components/organisms/dashboard/ai/Ai-Sidebar.jsx, lib/actions/ai/load-chat-history.js — NEXT_PUBLIC_AI_API_URL
components/stellar/StellarProvider.jsx — NEXT_PUBLIC_STELLAR_NETWORK
lib/config/axios.config.js, lib/config/firebase.config.js
README.md — Configuration section
What We Want
- Rewrite
.env.example to list exactly the variables the code reads — NEXT_PUBLIC_API_URL, NEXT_PUBLIC_AI_API_URL, NEXT_PUBLIC_STELLAR_NETWORK, NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, upload preset name(s), NEXT_PUBLIC_JITSI_DOMAIN, and the Firebase config vars (see 4) — each with a one-line comment. Remove NEXT_PUBLIC_CLOUDINARY_API_SECRET, NEXT_PUBLIC_CLOUDINARY_API_KEY, NEXT_PUBLIC_CLOUDINARY_URL, and DNB_API_URL.
- Add a small validated env module (e.g.
lib/config/env.js) using the already-installed zod: parse process.env once, constrain NEXT_PUBLIC_STELLAR_NETWORK to "testnet" | "mainnet", require URLs to be valid URLs, and fail the build/boot with a readable message listing what's missing. Route existing process.env reads through it.
- Refuse (in the schema) any variable name matching
NEXT_PUBLIC_.*SECRET so the mistake can't quietly return.
- Move the hardcoded Firebase web config in
lib/config/firebase.config.js into NEXT_PUBLIC_FIREBASE_* vars sourced from the env module (Firebase web keys are not secrets, but they differ per environment and don't belong hardcoded).
- Update
README.md's configuration section to the full table of variables.
Technical Context
zod is already a dependency; do not add @t3-oss/env-nextjs or similar.
- Server routes (
app/api/ai/*) run in Node — they can read non-NEXT_PUBLIC_ vars; consider renaming the AI URL to a server-only var there while keeping the public one for client components, or keep a single public var — document whichever you choose.
- Keep sensible dev fallbacks (localhost URLs) but log a clear one-line warning when a fallback engages.
Acceptance Criteria
What's Happening
Environment configuration is undocumented, unvalidated, and in one case actively dangerous:
.env.exampleincludesNEXT_PUBLIC_CLOUDINARY_API_SECRET=your_cloudinary_api_secret(andNEXT_PUBLIC_CLOUDINARY_API_KEY). Anything prefixedNEXT_PUBLIC_is inlined into the browser bundle — an API secret must never carry that prefix. The actual upload code (lib/utils/cloudinaryUpload.js) only usesNEXT_PUBLIC_CLOUDINARY_CLOUD_NAME+ an unsignedupload_preset, so the secret/key/url entries are unused and teach every contributor to paste a secret into a public-bundle variable.NEXT_PUBLIC_AI_API_URLis read inapp/api/ai/stream/route.js,app/api/ai/chat/route.js,components/organisms/dashboard/ai/Ai-Sidebar.jsx, andlib/actions/ai/load-chat-history.js(silently defaulting tohttp://localhost:8000), but does not appear in.env.example.DNB_API_URLis listed but nothing reads it.StellarProviderreadsNEXT_PUBLIC_STELLAR_NETWORKwith a silent"testnet"fallback,axios.config.jsfalls back to a hardcoded Render URL,firebase.config.jshardcodes the entire Firebase config in source instead of env vars.NEXT_PUBLIC_API_URL.Where to Find This
.env.examplelib/utils/cloudinaryUpload.js,hooks/useCloudinaryUpload.js— actual Cloudinary usageapp/api/ai/stream/route.js,components/organisms/dashboard/ai/Ai-Sidebar.jsx,lib/actions/ai/load-chat-history.js—NEXT_PUBLIC_AI_API_URLcomponents/stellar/StellarProvider.jsx—NEXT_PUBLIC_STELLAR_NETWORKlib/config/axios.config.js,lib/config/firebase.config.jsREADME.md— Configuration sectionWhat We Want
.env.exampleto list exactly the variables the code reads —NEXT_PUBLIC_API_URL,NEXT_PUBLIC_AI_API_URL,NEXT_PUBLIC_STELLAR_NETWORK,NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, upload preset name(s),NEXT_PUBLIC_JITSI_DOMAIN, and the Firebase config vars (see 4) — each with a one-line comment. RemoveNEXT_PUBLIC_CLOUDINARY_API_SECRET,NEXT_PUBLIC_CLOUDINARY_API_KEY,NEXT_PUBLIC_CLOUDINARY_URL, andDNB_API_URL.lib/config/env.js) using the already-installedzod: parseprocess.envonce, constrainNEXT_PUBLIC_STELLAR_NETWORKto"testnet" | "mainnet", require URLs to be valid URLs, and fail the build/boot with a readable message listing what's missing. Route existingprocess.envreads through it.NEXT_PUBLIC_.*SECRETso the mistake can't quietly return.lib/config/firebase.config.jsintoNEXT_PUBLIC_FIREBASE_*vars sourced from the env module (Firebase web keys are not secrets, but they differ per environment and don't belong hardcoded).README.md's configuration section to the full table of variables.Technical Context
zodis already a dependency; do not add@t3-oss/env-nextjsor similar.app/api/ai/*) run in Node — they can read non-NEXT_PUBLIC_vars; consider renaming the AI URL to a server-only var there while keeping the public one for client components, or keep a single public var — document whichever you choose.Acceptance Criteria
.env.examplecontains every variable the code reads and none it doesn't; noNEXT_PUBLIC_*SECRET*entries exist anywhere.npm run build(or booting dev) produces a clear, aggregated error naming the missing variable.NEXT_PUBLIC_STELLAR_NETWORK=foois rejected with a helpful message.npm run lintandnpm run buildpass with a correctly filled.env.local.