# Fix: Error: supabaseUrl is required.

`createClient` from `@supabase/supabase-js` was called with an empty or undefined URL. The environment variable you pass in was not set where the code ran. In Next.js the usual causes are a missing `NEXT_PUBLIC_` prefix on a variable read in the browser, a variable that Vercel has for Production but not for Preview, or a value added after the last build. Set the variable for the right environment and build again.

| Fact | Detail |
|---|---|
| Error | `supabaseUrl is required.` |
| Close relatives | `supabaseKey is required.` · `Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL.` · `@supabase/ssr: Your project's URL and API key are required to create a Supabase client!` |
| Where you see it | Browser console, `next build` output, Vercel build or function logs, the terminal |
| Usual cause | `process.env.NEXT_PUBLIC_SUPABASE_URL` is undefined at build time or in that environment |
| Usual fix | Set the variable for every environment that builds the app, then rebuild or redeploy |
| BugTape catches it | At request time on the server, and in the browser after BugTape starts. A failure at build time is only in your build log |

## What the error means

supabase-js validates the URL before it creates anything. It trims the value, and when nothing is left it throws `Error('supabaseUrl is required.')`. A value without `http://` or `https://` throws `Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL.` A missing key throws `supabaseKey is required.`

`@supabase/ssr` checks both values first and throws its own message. `createBrowserClient` says `@supabase/ssr: Your project's URL and API key are required to create a Supabase client!` and `createServerClient` says `Your project's URL and Key are required to create a Supabase client!` The cause and the fix are the same.

The `!` in `process.env.NEXT_PUBLIC_SUPABASE_URL!` only tells TypeScript to stop warning. At runtime the value is still `undefined`.

## Common causes

1. **The variable is not set in the environment that built the app.** On Vercel each variable is set for Production, Preview, Development or custom environments. A variable only in Production is missing from every preview deployment.
2. **The variable was added after the last deployment.** Vercel applies environment variable changes only to new deployments. Next.js inlines `NEXT_PUBLIC_` values into the browser bundle at build time, so a new value needs a new build.
3. **The browser reads a variable without the `NEXT_PUBLIC_` prefix.** `SUPABASE_URL` exists on the server and is undefined in the browser.
4. **A dynamic lookup.** `process.env[name]` or `const env = process.env; env.NEXT_PUBLIC_SUPABASE_URL` is not inlined, so it is undefined in the browser.
5. **The `.env` file is in the wrong place or has the wrong name.** Next.js loads `.env*` files only from the project root. Files inside `src/` are ignored. `.env.local` is not loaded when `NODE_ENV` is `test`.
6. **One Docker image or build is promoted to several environments.** `NEXT_PUBLIC_` values are frozen at build time, so the runtime environment cannot change them.
7. **Vite or another bundler.** Vite exposes only variables prefixed with `VITE_` through `import.meta.env`, so `process.env.SUPABASE_URL` is undefined there.

## How to fix it

### Set both variables with the names the code reads

```bash
# .env.local in the project root, next to package.json
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```

Restart `next dev` after you change `.env.local`. The publishable key is safe in the browser. The secret key is not, and must never get a `NEXT_PUBLIC_` prefix.

### Set them for every Vercel environment and redeploy

```bash
vercel env add NEXT_PUBLIC_SUPABASE_URL production
vercel env add NEXT_PUBLIC_SUPABASE_URL preview
vercel env add NEXT_PUBLIC_SUPABASE_URL development
vercel env pull .env.local   # copy the Development values to your machine
```

Then start a new deployment. The existing ones keep the values they were built with.

### Read the variables with static names

Write each variable out in full so Next.js can replace it at build time.

```ts
// lib/supabase/env.ts
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY;

if (!url || !key) {
  throw new Error(
    'Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY. ' +
      'Set them for this environment and rebuild.',
  );
}

export const supabaseUrl = url;
export const supabasePublishableKey = key;
```

A clear message names the variable, which saves a search the next time it happens.

### Create the client inside a function

A client created at the top of a module runs when the module is imported, which can be during `next build` or before anything else on the page has loaded. Creating it in a function moves the error to the first real use.

```ts
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr';
import { supabaseUrl, supabasePublishableKey } from './env';

export function createClient() {
  return createBrowserClient(supabaseUrl, supabasePublishableKey);
}
```

### Use the Vite prefix in Vite apps

```ts
import { createClient } from '@supabase/supabase-js';

export const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
);
```

## How to catch it in production with BugTape

Where this error happens decides whether BugTape sees it.

- **During `next build`.** When a page is prerendered, the error fails the build. Nothing is running yet, so no SDK reports it. Read the build log.
- **On the server at request time.** The Node.js SDK reports errors thrown in pages, Route Handlers, Server Actions and the proxy through Next.js `onRequestError`, on the Node.js runtime. The Edge runtime is not supported.
- **In the browser.** The browser SDK reports uncaught errors as soon as `init()` has run. A client created at the top of a module that loads with the first page can throw before BugTape starts, and that one is missed. A client created inside a function, on first use, is reported with the page URL and the steps before it.

Wire the server side once:

```ts
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') (await import('@bugtape/node')).init({ service: 'web' });
}

export async function onRequestError(...args: Parameters<typeof import('@bugtape/node').captureRequestError>) {
  if (process.env.NEXT_RUNTIME === 'nodejs') await (await import('@bugtape/node')).captureRequestError(...args);
}
```

For a missing variable you detect yourself, report it and keep the page usable. In the browser, `reportError(err)` fires the same window `error` event the SDK listens for. On the server, call `bugtape.captureException(err)`.

Every visitor to a broken deployment hits the same line, so the reports group into one issue with a count. Set `release` (on Vercel, `VERCEL_GIT_COMMIT_SHA`, which the Node.js SDK reads on its own) so you can see which deployment is missing the value, and a fix that does not hold reopens as a regression. Your coding agent can read the issue over MCP (`npx -y bugtape connect claude`, or `cursor` or `codex`). You review the pull request.

## FAQ

### Why does it work locally but fail on Vercel?

Your machine reads `.env.local`, which Vercel never sees. Add the variables in the project settings for each environment you deploy, then start a new deployment.

### Why does it work in production but fail in preview deployments?

The variable is set only for Production. Preview deployments use the Preview environment. Add the variable there, or for a specific branch, and redeploy.

### I set the variable but the error is still there. Why?

`NEXT_PUBLIC_` values are copied into the JavaScript bundle when the app is built. Changing the variable does not change a build that already exists. Start a new build. Locally, restart `next dev`.

### Is it safe to put the Supabase URL and key in NEXT_PUBLIC_ variables?

The project URL and the publishable (anon) key are meant for the browser. Your data is protected by row-level security policies. The secret key, or the legacy `service_role` key, bypasses those policies and must stay in server-only variables.

## Sources

- [supabase-js source: lib/helpers.ts](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/src/lib/helpers.ts) and [SupabaseClient.ts](https://github.com/supabase/supabase-js/blob/master/packages/core/supabase-js/src/SupabaseClient.ts) (exact messages), checked 2 Nov 2026.
- [@supabase/ssr source: createBrowserClient.ts](https://github.com/supabase/ssr/blob/main/src/createBrowserClient.ts) and [createServerClient.ts](https://github.com/supabase/ssr/blob/main/src/createServerClient.ts), checked 2 Nov 2026.
- [Next.js: Environment variables](https://nextjs.org/docs/app/guides/environment-variables) (`NEXT_PUBLIC_` inlined at build time, dynamic lookups, load order), checked 2 Nov 2026.
- [Vercel: Environment variables](https://vercel.com/docs/environment-variables) (environments, changes apply to new deployments, `vercel env pull`), checked 2 Nov 2026.
- [Vite: Env variables and modes](https://vite.dev/guide/env-and-mode), checked 2 Nov 2026.

## Related errors

- [Supabase Auth session missing](/guides/supabase-auth-session-missing/): `AuthSessionMissingError: Auth session missing!`
- [ReferenceError: window is not defined](/guides/window-is-not-defined/): `ReferenceError: window is not defined`
- [Vercel FUNCTION_INVOCATION_FAILED](/guides/vercel-function-invocation-failed/): `500: INTERNAL_SERVER_ERROR Code: FUNCTION_INVOCATION_FAILED`
- [Supabase row-level security violation](/guides/supabase-rls-violation/): `new row violates row-level security policy for table`
- [Firebase missing or insufficient permissions](/guides/firebase-missing-or-insufficient-permissions/): `FirebaseError: Missing or insufficient permissions.`

All guides: [Fix an error](/guides/).

[Start free](/console/register?utm_source=bugtape_seo&utm_campaign=error-guides). Free plan · No credit card · Set up in minutes. Or [try the live demo](/demo/?from=landing&start=1#capture) first.
