Bug Description
getURL() in packages/better-fetch/src/url.ts has an inconsistent return type. When basePath does not start with "http", it returns a string. When basePath does start with "http", it returns a URL object. Downstream code expects a string and calls .indexOf() on the result, which throws:
TypeError: _url.indexOf is not a function
Reproduction
import { createFetch } from "@better-fetch/fetch";
const $fetch = createFetch({
baseURL: "https://example.com/api",
});
// This triggers the error because getURL returns a URL object
// and internal code calls .indexOf() on it
await $fetch("/some-path");
This also affects better-auth when createAuthClient is given an absolute baseURL:
import { createAuthClient } from "better-auth/react";
// Any absolute URL triggers the bug
const authClient = createAuthClient({
baseURL: "https://portal.example.com",
});
// Every operation that goes through $fetch will throw _url.indexOf errors:
// - Session auto-fetch on component mount
// - signIn.social()
// - signUp.email()
// - signOut()
Root Cause
In packages/better-fetch/src/url.ts, the getURL function:
// String branch (correct) — when basePath is relative
if (!basePath.startsWith("http")) {
return `${basePath}${path}${queryParamString}`; // returns string ✓
}
// URL object branch (incorrect) — when basePath is absolute
const _url = new URL(`${path}${queryParamString}`, basePath);
return _url; // returns URL object ✗
Suggested Fix
Return _url.toString() or _url.href instead of the raw URL object:
const _url = new URL(`${path}${queryParamString}`, basePath);
-return _url;
+return _url.toString();
Alternatively, the function's return type could be string | URL with all callers updated to handle both, but .toString() is simpler and maintains backward compatibility.
Impact
This bug affects anyone using better-auth's client with an absolute baseURL (the default when using window.location.origin or any full URL). The errors cascade because better-auth/react's session atoms auto-fetch on mount, focus, and visibility changes — creating a torrent of errors on every page load.
Current Workaround
Setting baseURL to an empty string "" or a relative path forces the string-concatenation branch:
const authClient = createAuthClient({
baseURL: "", // relative — browser resolves against current origin
});
Versions Affected
Confirmed present in: v1.1.18, v1.1.19, v1.1.21 (latest), and main branch.
Bug Description
getURL()inpackages/better-fetch/src/url.tshas an inconsistent return type. WhenbasePathdoes not start with"http", it returns a string. WhenbasePathdoes start with"http", it returns a URL object. Downstream code expects a string and calls.indexOf()on the result, which throws:Reproduction
This also affects
better-authwhencreateAuthClientis given an absolutebaseURL:Root Cause
In
packages/better-fetch/src/url.ts, thegetURLfunction:Suggested Fix
Return
_url.toString()or_url.hrefinstead of the rawURLobject:const _url = new URL(`${path}${queryParamString}`, basePath); -return _url; +return _url.toString();Alternatively, the function's return type could be
string | URLwith all callers updated to handle both, but.toString()is simpler and maintains backward compatibility.Impact
This bug affects anyone using
better-auth's client with an absolutebaseURL(the default when usingwindow.location.originor any full URL). The errors cascade becausebetter-auth/react's session atoms auto-fetch on mount, focus, and visibility changes — creating a torrent of errors on every page load.Current Workaround
Setting
baseURLto an empty string""or a relative path forces the string-concatenation branch:Versions Affected
Confirmed present in: v1.1.18, v1.1.19, v1.1.21 (latest), and
mainbranch.