diff --git a/.changeset/five-cougars-throw.md b/.changeset/five-cougars-throw.md new file mode 100644 index 00000000000..4c4f2403ce4 --- /dev/null +++ b/.changeset/five-cougars-throw.md @@ -0,0 +1,29 @@ +--- +'@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). 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; }