From bceae9bc6d53cebe746eb5dfea56146bdaddca0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20H=C3=B6glund?= Date: Mon, 2 Feb 2026 14:30:45 +0100 Subject: [PATCH 1/2] Make useAuth().getToken() throw during SSR --- .changeset/five-cougars-throw.md | 71 +++++++++++++++++++ .../src/hooks/__tests__/useAuth.test.tsx | 25 +++++++ packages/react/src/hooks/utils.ts | 12 ++++ packages/shared/src/getToken.ts | 9 ++- packages/shared/src/types/hooks.ts | 5 ++ 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 .changeset/five-cougars-throw.md diff --git a/.changeset/five-cougars-throw.md b/.changeset/five-cougars-throw.md new file mode 100644 index 00000000000..9b59634802b --- /dev/null +++ b/.changeset/five-cougars-throw.md @@ -0,0 +1,71 @@ +--- +'@clerk/react': major +'@clerk/nextjs': major +'@clerk/react-router': major +'@clerk/tanstack-react-start': minor +'@clerk/shared': patch +--- + +`useAuth().getToken` is no longer `undefined` during server-side rendering, it is a function and calling it will throw. + +* If you are only using `getToken` in `useEffect`, event handlers or with non-suspenseful data fetching libraries, no change is necessary as these only trigger on the client. +* If you are using suspenseful data fetching libraries that do trigger during SSR, you likely have strategies in place to avoid calling `getToken` already, since this has never been possible. +* If you are using `getToken === undefined` checks to avoid calling it, know that it will now throw instead and you should catch and handle the error. + +```tsx +async function doThingWithToken(getToken: GetToken) { + try { + const token = await getToken(); + + // Use token + } catch (error) { + if (isClerkRuntimeError(error) && error.code === 'clerk_runtime_not_browser') { + // Handle error + } + } +} +``` + +To access auth data server-side, see the [`Auth` object reference doc](https://clerk.com/docs/reference/backend/types/auth-object). + + + + + + + + + + + + + + + +```tsx +const { getToken } = useAuth(); + +useEffect(() => { + if (getToken) { + getToken().then(token => { + // Use the token + }) + } +}); +``` + +```tsx +const { getToken } = useAuth(); + +useEffect(() => { + getToken + .then(() => { + // Use the token + }) + .catch((error) => { + if (isClerkRuntimeError(error) && error.code === 'clerk_runtime_not_browser') { + // Handle or ignore the error + } + }); +}); +``` diff --git a/packages/react/src/hooks/__tests__/useAuth.test.tsx b/packages/react/src/hooks/__tests__/useAuth.test.tsx index 70f86cac5b7..b395627fc15 100644 --- a/packages/react/src/hooks/__tests__/useAuth.test.tsx +++ b/packages/react/src/hooks/__tests__/useAuth.test.tsx @@ -78,6 +78,31 @@ describe('useAuth', () => { }); }); +describe('useAuth.getToken', () => { + test('throws an error if getToken is called in a non-browser environment', async () => { + const originalWindow = global.window; + + const { result } = renderHook(() => useAuth(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + // Set window to undefined to simulate non-browser environment + global.window = undefined as any; + + try { + await expect(result.current.getToken()).rejects.toThrow( + 'useAuth().getToken() can only be used in browser environments', + ); + } finally { + global.window = originalWindow; + } + }); +}); + describe('useDerivedAuth', () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/react/src/hooks/utils.ts b/packages/react/src/hooks/utils.ts index 9ac4cfbfe97..b895b755a70 100644 --- a/packages/react/src/hooks/utils.ts +++ b/packages/react/src/hooks/utils.ts @@ -1,3 +1,6 @@ +import { inBrowser } from '@clerk/shared/browser'; +import { ClerkRuntimeError } from '@clerk/shared/error'; + import type { IsomorphicClerk } from '../isomorphicClerk'; /** @@ -22,6 +25,15 @@ const clerkLoaded = (isomorphicClerk: IsomorphicClerk) => { */ export const createGetToken = (isomorphicClerk: IsomorphicClerk) => { return async (options: any) => { + if (!inBrowser()) { + throw new ClerkRuntimeError( + 'useAuth().getToken() can only be used in browser environments. To access auth data server-side, see the Auth object reference doc: https://clerk.com/docs/reference/backend/types/auth-object', + { + code: 'clerk_runtime_not_browser', + }, + ); + } + await clerkLoaded(isomorphicClerk); if (!isomorphicClerk.session) { return null; diff --git a/packages/shared/src/getToken.ts b/packages/shared/src/getToken.ts index 14c98f834cd..a9b77255990 100644 --- a/packages/shared/src/getToken.ts +++ b/packages/shared/src/getToken.ts @@ -37,9 +37,12 @@ function getWindowClerk(): LoadedClerk | undefined { async function waitForClerk(): Promise { if (!inBrowser()) { - throw new ClerkRuntimeError('getToken can only be used in browser environments.', { - code: 'clerk_runtime_not_browser', - }); + throw new ClerkRuntimeError( + 'getToken can only be used in browser environments. To access auth data server-side, see the Auth object reference doc: https://clerk.com/docs/reference/backend/types/auth-object', + { + code: 'clerk_runtime_not_browser', + }, + ); } const clerk = getWindowClerk(); diff --git a/packages/shared/src/types/hooks.ts b/packages/shared/src/types/hooks.ts index 622d588478b..812a0c29445 100644 --- a/packages/shared/src/types/hooks.ts +++ b/packages/shared/src/types/hooks.ts @@ -71,6 +71,11 @@ export type UseAuthReturn = signOut: SignOut; /** * A function that retrieves the current user's session token or a custom JWT template. Returns a promise that resolves to the token. See the [reference doc](https://clerk.com/docs/reference/javascript/session#get-token). + * + * > [!NOTE] + * > To access auth data server-side, see the [`Auth` object reference doc](https://clerk.com/docs/reference/backend/types/auth-object). + * + * @throws {ClerkRuntimeError} When called in a non-browser environment (code: `clerk_runtime_not_browser`) */ getToken: GetToken; } From 678b5e51c0e7ff14fd15cc7dc87350a04df7321c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20H=C3=B6glund?= Date: Mon, 2 Feb 2026 15:05:23 +0100 Subject: [PATCH 2/2] Remove leftover cruft in the changelog --- .changeset/five-cougars-throw.md | 42 -------------------------------- 1 file changed, 42 deletions(-) diff --git a/.changeset/five-cougars-throw.md b/.changeset/five-cougars-throw.md index 9b59634802b..4c4f2403ce4 100644 --- a/.changeset/five-cougars-throw.md +++ b/.changeset/five-cougars-throw.md @@ -27,45 +27,3 @@ async function doThingWithToken(getToken: GetToken) { ``` To access auth data server-side, see the [`Auth` object reference doc](https://clerk.com/docs/reference/backend/types/auth-object). - - - - - - - - - - - - - - - -```tsx -const { getToken } = useAuth(); - -useEffect(() => { - if (getToken) { - getToken().then(token => { - // Use the token - }) - } -}); -``` - -```tsx -const { getToken } = useAuth(); - -useEffect(() => { - getToken - .then(() => { - // Use the token - }) - .catch((error) => { - if (isClerkRuntimeError(error) && error.code === 'clerk_runtime_not_browser') { - // Handle or ignore the error - } - }); -}); -```