Fix: Multiple GoTrueClient instances detected (Supabase)
This warning means createClient() ran more than once — a module-scope client re-created per render, or two auth SDKs sharing the same localStorage key.
Multiple GoTrueClient instances detected in the same browser context. It is not currently supported to run multiple instances of GoTrueClient. This may produce undefined behavior fires because Supabase's auth module (GoTrue) is
built as a per-tab singleton that owns one localStorage key
(sb-<project-ref>-auth-token by default) for the session, its refresh timer,
and its onAuthStateChange subscribers. A second createClient() call in the
same browser tab creates a second GoTrue instance that fights the first one
over that same key — both try to refresh the token, both listen for auth
changes, and one can overwrite the other's session write mid-refresh.
Cause 1: createClient() called on every render or every import#
This is the most common version, and it's a module-instantiation bug, not an auth bug:
// ❌ lib/supabase.ts — re-runs createClient() every time this module is imported
import { createClient } from '@supabase/supabase-js';
export function getSupabase() {
return createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!);
}Every call site that does const supabase = getSupabase() gets its own
GoTrue instance. The fix is a module-scope singleton — create the client
once, at module load time, and export the instance itself:
// ✅ lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);// every call site imports the same instance
import { supabase } from '@/lib/supabase';In a Next.js App Router project, the equivalent bug is calling
createBrowserClient() from @supabase/ssr inside a component body instead
of a hook created once:
// ❌ components/Nav.tsx — new client on every render
import { createBrowserClient } from '@supabase/ssr';
export function Nav() {
const supabase = createBrowserClient(url, key); // re-created every render
}// ✅ lib/supabase-browser.ts — one instance, reused via a hook
import { createBrowserClient } from '@supabase/ssr';
let client: ReturnType<typeof createBrowserClient>;
export function getSupabaseBrowserClient() {
if (!client) {
client = createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
return client;
}Cause 2: hot-reload duplicating the client in development#
Next.js Fast Refresh can re-evaluate a module without a full page reload,
which re-runs createClient() at module scope and can leave a second,
orphaned GoTrue instance listening in the background — this version of the
warning tends to appear only in next dev, disappears on a hard refresh, and
never shows up in production. Guarding the singleton against
globalThis survives Fast Refresh cleanly:
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
const g = globalThis as unknown as { __supabase?: ReturnType<typeof createClient> };
export const supabase =
g.__supabase ??
(g.__supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
));Cause 3: a second auth SDK sharing the browser context#
This is the case Stack Overflow question 78016419 describes: an app using Clerk (or NextAuth, or Firebase Auth) for the primary sign-in flow and Supabase's own client for database access ends up with two independent auth stacks in the same tab. If the Supabase client is only being used for its Postgres/Storage access — not for Supabase Auth itself — disable GoTrue's session persistence so it stops trying to own a session at all:
export const supabase = createClient(url, key, {
auth: {
persistSession: false,
autoRefreshToken: false,
},
});Pass the external provider's JWT explicitly per request instead
(Authorization: Bearer <token> via
Third-Party Auth
if using Clerk/Auth0/Firebase with Supabase RLS), rather than letting two
clients race to manage localStorage.
Verifying the fix#
grep -rn "createClient(" src app lib— count the call sites; there should be exactly one for the browser client (server-side clients created per request are fine and unrelated to this warning).- Reload the app in a clean incognito tab; the warning is logged once, at most, on the very first client creation — not repeatedly per navigation.
- Confirm
localStorage.getItem('sb-<project-ref>-auth-token')in DevTools holds one consistent session object across page navigations instead of flipping between two differentexpires_atvalues.
When two clients are legitimate: separate storageKey per client#
Not every case of this warning is a bug to eliminate. A multi-tenant app that
connects to more than one Supabase project in the same browser tab — one project per
tenant, or a primary project plus a separate analytics/reporting project — genuinely
needs two independent GoTrueClient instances, and collapsing them into one client
is not an option because each targets a different Supabase URL and anon key. The
warning still fires here because both clients default to the same localStorage key
pattern unless told otherwise, so the fix is not removing a client, it's giving each
one its own storage key:
export const supabaseA = createClient(urlA, keyA, {
auth: { storageKey: 'sb-tenant-a-auth-token' },
});
export const supabaseB = createClient(urlB, keyB, {
auth: { storageKey: 'sb-tenant-b-auth-token' },
});With distinct storageKey values, both clients persist their sessions under separate
localStorage entries and stop overwriting each other's refresh state — the warning
disappears because there is no longer a shared key to race over, even though two
GoTrueClient instances are still running by design. This is the same mechanism a
multi-tenant architecture needs for tenant-scoped auth; see
Supabase Multi-Tenant Architecture: Best Practices 2026
for how tenant isolation extends beyond just the client instance into RLS policies and
subdomain routing. Do not reach for persistSession: false here as a shortcut — that
disables session persistence entirely for that client, which breaks the tenant it's
actually supposed to authenticate.
A third-party widget bundling its own Supabase client#
The warning sometimes has nothing to do with your own code. An embedded third-party
script — a support-chat widget, a feedback tool, or an analytics snippet that itself
uses Supabase under the hood and happens to point at the same project (same URL and
anon key you use) — instantiates its own GoTrueClient inside the same browser tab,
completely outside your bundle and outside grep's reach. This is the hardest version
of the bug to find because grep -rn "createClient(" across your own repo comes back
clean; the second instance is shipped by a script tag or an npm package you didn't
write.
To confirm this is the cause, open DevTools → Sources (or Network, for a script tag)
and search the loaded scripts for @supabase/supabase-js or GoTrueClient outside
your own bundle's chunk names. If a vendor script is the source, the two realistic
fixes are: ask the vendor for a build that lets you pass an existing Supabase client
instance instead of creating its own (many analytics/chat SDKs support this via an
init option), or, if that's not available, isolate the vendor's client from yours by
scoping your own client's session to a non-default storageKey as shown above — it
won't stop the vendor's client from existing, but it stops the two from fighting over
the same localStorage entry. If the vendor client only needs read access and never
performs its own sign-in, check whether it can run against the
service_role key server-side instead of a
second browser client entirely, removing the second GoTrueClient from the tab.
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Related fixes & guides
- Stop the Supabase getSession() Security Warning
- Supabase Edge Function BOOT_ERROR: Fix "Failed to Bootstrap"
- Fix: Event handlers cannot be passed to Client props
- Supabase SSR Sessions in Next.js App Router (2026 Guide)
- Complete Guide to Building SaaS with Next.js and Supabase
- Supabase + Google OAuth on Next.js 15: Working Guide (2026)
- Fix Supabase: Invalid Refresh Token Already Used
- Supabase Auth Callback Redirect Not Working? Next.js Fix
- Supabase vs Firebase in 2026: The Honest Comparison
- Supabase AuthSessionMissingError in Middleware: 5 Fixes

