Ship it · 2 min
Environment variables
Every variable, what breaks without it, and the two mistakes that produce a 500 with no useful message.
| Variable | Needed | Without it |
|---|---|---|
NEXT_PUBLIC_SITE_URL | Always | Metadata, sitemap and Stripe redirects break |
NEXT_PUBLIC_SUPABASE_URL | Always | No auth, no data |
NEXT_PUBLIC_SUPABASE_KEY | Always | Same |
SUPABASE_SECRET_KEY | Always | The webhook cannot write |
RESEND_API_KEY | For email | Sends are skipped, logged, not thrown |
EMAIL_FROM | For email | Resend rejects the send |
STRIPE_SECRET_KEY | For money | /api/checkout returns 503, app fine |
STRIPE_WEBHOOK_SECRET | For money | The webhook returns 503 |
ADMIN_EMAILS | For /admin | Nobody can open it |
SUPABASE_ACCESS_TOKEN | For db:push only | Never needed at runtime |
NEXT_PUBLIC means public
Anything prefixed NEXT_PUBLIC_ is inlined into the JavaScript bundle at
build time. It is in your customer's browser, readable in devtools. That is
correct for the Supabase publishable key, which is designed for it and is
useless without a session because Row Level Security does the work.
It is catastrophic for SUPABASE_SECRET_KEY, which bypasses RLS entirely.
Never prefix a secret.
Empty is worse than missing
This is the one that produces a 500 on every route with nothing in the log:
const base = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000";
new URL("/pricing", base);?? only falls back on null and undefined. A variable set to an empty
string passes straight through, and new URL("/pricing", "") throws. If it is
in the root layout, every page dies.
Delete the variable rather than blanking it, or guard on truthiness:
const base = process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000";Plain, not sensitive
On Vercel, a variable marked Sensitive is write-only: you cannot read it
back, and it is not inlined into the bundle. For a NEXT_PUBLIC_ variable that
means the browser receives undefined, and the app breaks in production in a
way it never did locally.
Use Encrypted, which is the default. Sensitive is for values you will never need to verify, and you will always need to verify them.
Rotating
Anything that has been pasted into a chat, a ticket or a screenshot is burned. Rotate it. It takes a minute in every dashboard here, and it is the cheapest security work you will ever do.
Something wrong or missing on this page? Tell us.