Fix supabaseUrl is required on Vercel
Error: supabaseUrl is required. on a Vercel build means process.env.NEXT_PUBLIC_SUPABASE_URL was empty when the page was compiled — the variable exists locally but isn't set for the environment being built. Add it in Vercel for Production, Preview and Development, then redeploy; a redeploy is required because Next.js inlines the value at build time.
It works locally. It works in vercel dev. Then a deploy fails with a trace that looks like this:
Error: supabaseUrl is required.
at /vercel/path0/.next/server/chunks/286.js:1:114738
The /vercel/path0/ prefix is just Vercel's build working directory, and the minified chunk path tells you nothing useful. The message itself is the whole story: createClient() was handed undefined as its first argument, and supabase-js throws before doing anything else.
The thread on Vercel's own forum is representative: it's not a code defect, it's environment configuration. There are four traps, and the fix depends on which one you hit.
Why the build fails when the code is fine
NEXT_PUBLIC_* variables aren't read at runtime. Next.js inlines them into the JavaScript at build time, replacing process.env.NEXT_PUBLIC_SUPABASE_URL with a string literal. If the variable is absent during next build, the literal it bakes in is undefined, permanently, for that deployment.
That gives the failure its two defining traits: it can happen during build (when a page is prerendered), and adding the variable afterwards changes nothing until you rebuild.
The four reasons it's undefined on Vercel
1. The variable is scoped to Production only. This is the most common one by a wide margin. Vercel scopes each environment variable to Production, Preview, and Development independently. Add it while looking at Production, and every preview deployment from a branch or PR builds without it. Symptom: main deploys fine, PR previews fail.
2. It was added after the last deploy. Environment variables are read at build time, so an existing deployment keeps the values it was built with. You need a fresh build — and Vercel's "Redeploy" defaults to reusing the existing build cache, which can skip the recompile entirely. Untick Use existing Build Cache.
3. It's in .env.local, which is gitignored. Correctly, by the way. But that means your machine has the value and the build machine doesn't. .env.local is never uploaded, and it should never be committed.
4. The name doesn't match. A trailing space pasted along with the value, NEXT_PUBLIC_SUPABASE_URL vs NEXT_PUBLIC_SUPABASE_PROJECT_URL, or a server-side name (SUPABASE_URL) referenced from a client component where only NEXT_PUBLIC_ names survive.
How do I fix supabaseUrl is required on Vercel?
Set both public variables for all three environments, then redeploy without the build cache. In Vercel → Project → Settings → Environment Variables:
| Name | Value | Environments |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | https://<ref>.supabase.co | Production, Preview, Development |
NEXT_PUBLIC_SUPABASE_ANON_KEY | your anon / publishable key | Production, Preview, Development |
SUPABASE_SERVICE_ROLE_KEY | your service role key | Production, Preview — no NEXT_PUBLIC_ |
Production and Preview are the two that affect builds; the Development scope only feeds vercel dev and vercel env pull, but set it too so local and remote stay in step. One caveat on the audit below: vercel env add now marks new Production/Preview values as sensitive, meaning they can't be read back from the dashboard or vercel env ls. Names still list, so you can diff names — not values.
Then fail loudly instead of throwing from inside a vendor package, so the next occurrence names itself:
// lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!url || !anonKey) {
throw new Error(
"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY. " +
"Set them in Vercel for this environment and redeploy."
);
}
export const createClient = () => createBrowserClient(url, anonKey);
That turns a minified chunk offset into a sentence naming the variable and the environment.
The fix that turns a build error into a breach
Here's the shortcut that resolves the error and costs you your database. The reasoning is superficially sound — "NEXT_PUBLIC_ variables are the ones that work, so make them all NEXT_PUBLIC_" — and it does make the build go green:
import { createClient } from "@supabase/supabase-js";
// Prefixed everything with NEXT_PUBLIC_ so the Vercel build would stop failing.
export const admin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!
);
- Criticallib/supabase/admin.ts:6
Service role key exposed to the client
Never prefix the service role key with NEXT_PUBLIC_. Read it only in server code via process.env.SUPABASE_SERVICE_ROLE_KEY, and rotate the key immediately since it has been exposed. - Criticallib/supabase/admin.ts:6
Secret exposed through NEXT_PUBLIC_
Drop the NEXT_PUBLIC_ prefix and read the value only on the server. Publishable/anon keys are fine to expose; secret keys, tokens, and passwords are not — rotate any that have shipped.
NEXT_PUBLIC_ doesn't mean "available at build time". It means inlined into the browser bundle and shipped to every visitor. The service role key bypasses every RLS policy you have, so publishing it hands anyone with devtools full read and write access to every table. Nothing breaks, no warning appears, and the deploy is green — which is precisely why this one survives to production. It's the same mechanism behind every NEXT_PUBLIC_ key leak, just with the worst possible key attached.
The anon key is different and safe here: it's designed to be public and is constrained by your policies. The rule is about what the name implies, not the prefix itself — publishable values belong in the bundle, secrets never do.
If you've already deployed with a NEXT_PUBLIC_ service role key, treat it as burned: rotate it in the Supabase dashboard, remove the prefix, and redeploy. The old bundle is cached in browsers and CDNs and may be in your git history.
Quick self-check
# Any secret hiding behind a public prefix?
grep -rn "NEXT_PUBLIC_.*\(SERVICE_ROLE\|SECRET\|PASSWORD\)" . --exclude-dir=node_modules
# Compare what your code needs against what Vercel actually has
grep -rho "process\.env\.[A-Z_]*" --include=*.ts --include=*.tsx . | sort -u
vercel env ls
Run the last two together and diff them. Every name your code reads should appear in vercel env ls for the environment you're deploying to — including Preview, which is the one everyone forgets.
FAQ
What does "supabaseUrl is required" mean?
supabase-js received undefined instead of a project URL. The environment variable holding it was empty at the moment the code ran or was compiled.
Why does it only fail on Vercel and not locally?
Your .env.local is gitignored, so it never reaches the build machine. Vercel builds only see variables configured in project settings for that specific environment.
I added the variable and it still fails. Why?
NEXT_PUBLIC_ values are inlined at build time, so an existing deployment keeps the old (empty) value. Redeploy — and untick "Use existing Build Cache", which can otherwise skip the recompile.
Should I just prefix the service role key with NEXT_PUBLIC_ to make it work?
No. That inlines it into the browser bundle. The service role key bypasses RLS entirely, so it becomes a full database compromise. Keep it as SUPABASE_SERVICE_ROLE_KEY, read it only in server code, and rotate it if it has ever shipped with a public prefix.
Is it safe to put the anon key in Vercel as a public variable? Yes — the anon key is meant to be public and is gated by your RLS policies. That guarantee only holds if RLS is actually enabled on your tables.
Why does the error mention /vercel/path0/? That's just the directory Vercel checks your repository out into during a build. It carries no diagnostic meaning beyond "this happened on the build machine".
Catch this before it ships — free
GuardLayer scans every push for this and 28 other Next.js + Supabase issues, with the exact fix inline.
No signup, no card — your code is scanned in memory and never stored.