Environment Variables
The rule that matters
Anything prefixed NEXT_PUBLIC_ is inlined into the browser bundle and
readable by anyone who visits the site. No secret may ever carry that prefix.
This has been got wrong before in this codebase: a Gemini API key was exposed this way and had to be rotated.
Client variables
Set in Vercel, or in .env.local for development.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_CONVEX_URL | Convex deployment URL. Also derives the allowed remote image host. The app fails at startup with a clear message if it is missing. |
NEXT_PUBLIC_VAPID_PUBLIC_KEY | Web Push public key. Public by design — the private half lives server-side. |
Server variables
Set with npx convex env set <NAME> <value>, never in a file.
| Variable | Purpose |
|---|---|
FRONTEND_SITE_URL | Base URL for links in emails and shared reports |
GEMINI_API_KEY | Google Gemini, used by convex/functions/ai.ts |
VAPID_PUBLIC_KEY | Web Push, public half |
VAPID_PRIVATE_KEY | Web Push, private half |
VAPID_EMAIL | Contact address required by the Web Push spec |
SMTP_HOST | Mail server |
SMTP_PORT | 465 for implicit TLS, 587 for STARTTLS |
SMTP_USER | Mail account |
SMTP_PASSWORD | Mail password or app password |
SMTP_FROM | Envelope sender address |
Generating VAPID keys
node -e "console.log(require('web-push').generateVAPIDKeys())"Set the public key in both places — as VAPID_PUBLIC_KEY in Convex and as
NEXT_PUBLIC_VAPID_PUBLIC_KEY in Vercel. They must match, or push subscriptions
are rejected.
Checking what is set
npx convex env list.env.example in the repository root is the authoritative list. Keep it current
when adding a variable.
Last updated on