@supabase/ssr browser client auth methods hang indefinitely due to orphaned Web Locks (potentially React Strict Mode compatibility issue)
Description
(N.b. - written with AI but it's done a far more comprehensive job than I would have - hope that's OK)
The @supabase/ssr browser client's auth methods (getUser(), signInWithPassword(), etc.) hang indefinitely without making any network requests when a Web Lock from a previous auth operation is not properly released.
The Supabase auth client uses the browser's Web Locks API (navigator.locks) to prevent concurrent auth operations. When a lock is acquired but not released (e.g., due to React Strict Mode unmounting a component mid-operation, or an abort signal firing), all subsequent auth calls queue behind the orphaned lock and hang forever.
The error AbortError: signal is aborted without reason appears in the console from locks.ts, indicating the lock acquisition was aborted, but the lock remains held.
To Reproduce
- Create a Next.js 14+ app with
@supabase/ssr (React Strict Mode is enabled by default)
- Create a client component that calls
supabase.auth.getUser() in a useEffect:
"use client";
import { useEffect } from "react";
import { createClient } from "@/lib/supabase/client"; // uses createBrowserClient
export default function SettingsPage() {
const supabase = createClient();
useEffect(() => {
const fetchUser = async () => {
const {
data: { user },
} = await supabase.auth.getUser(); // Hangs here
console.log(user);
};
fetchUser();
}, [supabase]);
return <div>Settings</div>;
}
- Navigate to the page - it may work initially
- Trigger React Strict Mode's unmount/remount cycle (happens automatically in dev) or navigate away while auth is in progress
- Subsequent auth calls hang indefinitely
Verification: Run in browser console:
navigator.locks.query().then(console.log);
You'll see:
{
held: [{ name: "lock:sb-xxx-auth-token", mode: "exclusive" }],
pending: [{ name: "lock:sb-xxx-auth-token", mode: "exclusive" }]
}
A lock is held and never released, with pending requests queued behind it.
Expected behavior
- Auth methods should complete or fail within a reasonable timeout, not hang indefinitely
- If a lock acquisition is aborted (e.g., component unmount), the lock should be properly released
- The auth client should be resilient to React Strict Mode's double-mount behavior in development
System information
- OS: macOS 15.7
- Browser: Chrome 144.0.7559.112
- Version of @supabase/ssr: 0.8.0
- Version of @supabase/supabase-js: 2.86.0
- Version of Node.js: (Next.js 14+)
- React: 18+ with Strict Mode enabled (Next.js default)
Additional context
Workaround: Close the browser completely to release orphaned locks, then reopen.
HAR file evidence: Network capture shows zero requests to Supabase auth endpoints when the hang occurs - the client is stuck internally waiting for the Web Lock, not waiting for a network response.
Related: This may be related to how @supabase/gotrue-js handles AbortController signals in locks.ts. When the abort fires, the lock holder should release the lock, but it appears the lock remains held.
Potential fixes:
- Add a timeout to lock acquisition that releases the lock and throws an error
- Use
navigator.locks.request() with { steal: true } option to force-acquire stuck locks
- Better handling of React Strict Mode's component lifecycle (mount → unmount → remount)
- Add a
forceUnlock() method or automatic recovery mechanism
@supabase/ssrbrowser client auth methods hang indefinitely due to orphaned Web Locks (potentially React Strict Mode compatibility issue)Description
(N.b. - written with AI but it's done a far more comprehensive job than I would have - hope that's OK)
The
@supabase/ssrbrowser client's auth methods (getUser(),signInWithPassword(), etc.) hang indefinitely without making any network requests when a Web Lock from a previous auth operation is not properly released.The Supabase auth client uses the browser's Web Locks API (
navigator.locks) to prevent concurrent auth operations. When a lock is acquired but not released (e.g., due to React Strict Mode unmounting a component mid-operation, or an abort signal firing), all subsequent auth calls queue behind the orphaned lock and hang forever.The error
AbortError: signal is aborted without reasonappears in the console fromlocks.ts, indicating the lock acquisition was aborted, but the lock remains held.To Reproduce
@supabase/ssr(React Strict Mode is enabled by default)supabase.auth.getUser()in auseEffect:Verification: Run in browser console:
You'll see:
A lock is held and never released, with pending requests queued behind it.
Expected behavior
System information
Additional context
Workaround: Close the browser completely to release orphaned locks, then reopen.
HAR file evidence: Network capture shows zero requests to Supabase auth endpoints when the hang occurs - the client is stuck internally waiting for the Web Lock, not waiting for a network response.
Related: This may be related to how
@supabase/gotrue-jshandles AbortController signals inlocks.ts. When the abort fires, the lock holder should release the lock, but it appears the lock remains held.Potential fixes:
navigator.locks.request()with{ steal: true }option to force-acquire stuck locksforceUnlock()method or automatic recovery mechanism