If your AI-built (or hand-built) Next.js app dies during next build with Error: supabaseUrl is required., the usual culprit is one line: a Supabase client created at the top level of a route file, evaluated at build time when your env vars aren't there.
We reproduced it on a small synthetic app, applied the smallest fix, and confirmed the build went from failing to passing. Below: the exact error, the cause, the fix, how we verified it, and what we did not test.
The exact error
From our reproduction (npx next build, no .env file, no Supabase vars set):
Collecting page data using 5 workers ...
Error: Failed to collect configuration for /api/waitlist
at ignore-listed frames {
[cause]: Error: supabaseUrl is required.
at module evaluation (app/api/waitlist/route.js:3:7)
> 3 | const supabase = createClient(
...
> Build error occurred
Error: Failed to collect page data for /api/waitlist
Exit code 1. The same text shows up in public reports, e.g. vercel/next.js#69095 (Vercel build, Next 14.2.3).
Versions we used
| Item | Version |
|---|---|
| Node.js | 20.19.2 |
| npm | 11.21.0 |
| next | 16.4.2 (Turbopack build) |
| react | 19.3.1 |
| @supabase/supabase-js | 2.109.0 |
| OS | Linux, single machine |
Cause
During next build, Next.js loads your route modules to collect page data. If a route file does this at module scope:
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
…then createClient runs during the build. If the variable is missing in the build environment (not in .env.local, not set on the host, misspelled, or scoped only to Production while you're building a Preview), createClient throws and the whole build fails. AI coding tools generate this top-level pattern a lot.
Smallest working fix
Create the client inside a function and call it in the handler.
Before (fails):
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
export async function POST(req) {
const { email } = await req.json();
const { error } = await supabase.from("waitlist").insert({ email });
return Response.json({ ok: !error });
}
After (builds):
import { createClient } from "@supabase/supabase-js";
function getSupabase() {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!url || !key) {
throw new Error("Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY");
}
return createClient(url, key);
}
export async function POST(req) {
try {
const { email } = await req.json();
const { error } = await getSupabase().from("waitlist").insert({ email });
return Response.json({ ok: !error });
} catch (e) {
return Response.json({ ok: false, error: e.message }, { status: 500 });
}
}
If you use an AI coding tool, a prompt like this describes the change (we did not test the prompt in any tool; review the diff yourself):
My Next.js build fails with "Error: supabaseUrl is required." Find every file that calls createClient from @supabase/supabase-js at the top level of a module. Move each call into a getSupabase() function that reads process.env at request time and throws a clear error naming the missing variable. Don't rename variables, don't print key values, don't change anything else. Show me the diff.
How we verified it
All runs on 2026-10-18, no .env file, no Supabase variables set:
-
Before:
npx next build→ failed with the error above, exit code 1. -
After:
npx next build→ exit code 0; output listedƒ /api/waitlistas a dynamic route. -
After, runtime with missing keys:
npx next start, thenPOST /api/waitlistwith a fake email →HTTP/1.1 500and{"ok":false,"error":"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY"}.
What the fix does not do: it doesn't supply your keys. It makes the build pass and moves the failure to a clear, request-time error. If your deployed app needs Supabase, you still have to set the variables on your host. NEXT_PUBLIC_ values are inlined at build time, so redeploy after changing them (documented Next.js behavior; not re-tested here).
Not verified
To be upfront about the limits:
- No real Supabase project: we never made a successful insert or any network call to Supabase.
- No Vercel, Netlify or other host build environment.
- No Lovable, Bolt, Cursor or v0 output. Our app is hand-written and synthetic, modeled on the pattern in public reports.
- No other versions (including Next 14), the Pages Router, or Vite/React apps.
- The AI prompt above was not run in any tool.
- The "placeholder env vars in CI" workaround was not tested.
- Other causes with the same symptom (misspelled or un-prefixed variable names, missing
.env.local) were not reproduced.
Quick troubleshooting
- Build passes, live app returns "Missing NEXT_PUBLIC_SUPABASE_URL…": set the variable on your host with the exact name, then redeploy.
-
Same build error after the fix: another file still creates a client at the top level. Search for
createClient(. -
Works locally, fails on host:
.env.localusually isn't committed, so the host never sees it.
Security: never put a service-role key in a NEXT_PUBLIC_ variable, since it ships to every browser. And don't paste real keys into AI chats or issues.
The original guide, with the before/after files, is on The Agent Ledger: supabaseUrl is required: next build fix.
AI disclosure: This article was written with the help of AI. The reproduction and the before/after build runs described above were actually performed and checked against the logs before publishing.
Top comments (1)
빌드가 통과하는 것과 실제 API 요청이 성공하는 것을 분리해서 적어주셔서 수정 범위를 이해하기 좋네요. 다음 검증에는 정상 환경 변수를 넣었을 때의 요청 결과와, Preview·Production 각각에서 필요한 변수의 존재 여부만 확인하는 체크도 있으면 좋겠습니다. 키 값 자체는 출력하지 않는 경계도 그대로 유지하고요.