Multiple GoTrueClient instances detected in the same browser context. It is fires because Supabase's auth module (GoTrue) is
not currently supported to run multiple instances of GoTrueClient. This may
produce undefined behavior
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.
Related Incidents
- Supabase Storage "new row violates row-level security policy"
- Supabase RLS Policy Not Working — Debug Checklist
- Supabase Auth vs Clerk (2026 Honest Comparison)
- Next.js + Supabase SSR Session Management
- Fix "cookies() should be awaited" Error in Next.js 15
Originally published at https://www.iloveblogs.blog
Top comments (0)