From a776bbbef3d57caf4be9b8339648345c1d75c443 Mon Sep 17 00:00:00 2001 From: rain Date: Thu, 16 Jul 2026 12:46:21 -0400 Subject: [PATCH] docs: avoid Clerk token refresh reconnects --- .../00500-authentication/00300-Clerk.md | 25 ++++++++++++++++--- .../00500-authentication/00300-Clerk.md | 25 ++++++++++++++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/docs/docs/00200-core-concepts/00500-authentication/00300-Clerk.md b/docs/docs/00200-core-concepts/00500-authentication/00300-Clerk.md index 13df43bf929..4247d0c8b3f 100644 --- a/docs/docs/00200-core-concepts/00500-authentication/00300-Clerk.md +++ b/docs/docs/00200-core-concepts/00500-authentication/00300-Clerk.md @@ -78,6 +78,7 @@ import React, { useContext, useEffect, useMemo, + useRef, useState, } from 'react'; import { useAuth, RedirectToSignIn } from '@clerk/clerk-react'; @@ -95,12 +96,17 @@ export function useClerkToken() { /** * ClerkTokenProvider: * - If signed out: renders Clerk's redirect component. - * - If signed in: loads a Clerk session token (JWT) and provides it via context. + * - If signed in: loads the initial Clerk session token (JWT) and provides it + * via context. * * Note: * - getToken() returns a token suitable for sending to your backend. If you have * configured a specific JWT template in Clerk, pass its name via * getToken({ template: "" }). + * - The token is intentionally captured once per signed-in session. Clerk may + * refresh its session token in the background, but replacing this context + * value would rebuild the SpacetimeDB connection. Keep the existing + * connection open until the user signs out or you intentionally reconnect. */ export function ClerkTokenProvider({ children, @@ -111,6 +117,7 @@ export function ClerkTokenProvider({ const [token, setToken] = useState(null); const [error, setError] = useState(null); + const tokenRef = useRef(null); useEffect(() => { let cancelled = false; @@ -120,7 +127,15 @@ export function ClerkTokenProvider({ // IMPORTANT: if signed out, clear any cached token if (!isSignedIn) { - if (!cancelled) setToken(null); + if (!cancelled) { + tokenRef.current = null; + setToken(null); + } + return; + } + + // Keep the token stable for the lifetime of this signed-in connection. + if (tokenRef.current) { return; } @@ -133,7 +148,10 @@ export function ClerkTokenProvider({ throw new Error('Clerk returned no session token.'); } - if (!cancelled) setToken(t); + if (!cancelled) { + tokenRef.current = t; + setToken(t); + } } catch (e) { if (!cancelled) setError(e as Error); } @@ -215,6 +233,7 @@ Update your `App.tsx` file to: 2. Pass it to the `DbConnection` builder using `.withToken(...)`. This mirrors the Auth0 flow: SpacetimeDB receives a bearer token (JWT) and can validate it server-side. +The token returned by `useClerkToken` stays stable while the user remains signed in, so Clerk's background token refreshes do not rebuild the `SpacetimeDBProvider` connection. diff --git a/docs/versioned_docs/version-1.12.0/00200-core-concepts/00500-authentication/00300-Clerk.md b/docs/versioned_docs/version-1.12.0/00200-core-concepts/00500-authentication/00300-Clerk.md index 1fd72d009d4..a92e06384d2 100644 --- a/docs/versioned_docs/version-1.12.0/00200-core-concepts/00500-authentication/00300-Clerk.md +++ b/docs/versioned_docs/version-1.12.0/00200-core-concepts/00500-authentication/00300-Clerk.md @@ -78,6 +78,7 @@ import React, { useContext, useEffect, useMemo, + useRef, useState, } from 'react'; import { useAuth, RedirectToSignIn } from '@clerk/clerk-react'; @@ -95,12 +96,17 @@ export function useClerkToken() { /** * ClerkTokenProvider: * - If signed out: renders Clerk's redirect component. - * - If signed in: loads a Clerk session token (JWT) and provides it via context. + * - If signed in: loads the initial Clerk session token (JWT) and provides it + * via context. * * Note: * - getToken() returns a token suitable for sending to your backend. If you have * configured a specific JWT template in Clerk, pass its name via * getToken({ template: "" }). + * - The token is intentionally captured once per signed-in session. Clerk may + * refresh its session token in the background, but replacing this context + * value would rebuild the SpacetimeDB connection. Keep the existing + * connection open until the user signs out or you intentionally reconnect. */ export function ClerkTokenProvider({ children, @@ -111,6 +117,7 @@ export function ClerkTokenProvider({ const [token, setToken] = useState(null); const [error, setError] = useState(null); + const tokenRef = useRef(null); useEffect(() => { let cancelled = false; @@ -120,7 +127,15 @@ export function ClerkTokenProvider({ // IMPORTANT: if signed out, clear any cached token if (!isSignedIn) { - if (!cancelled) setToken(null); + if (!cancelled) { + tokenRef.current = null; + setToken(null); + } + return; + } + + // Keep the token stable for the lifetime of this signed-in connection. + if (tokenRef.current) { return; } @@ -133,7 +148,10 @@ export function ClerkTokenProvider({ throw new Error('Clerk returned no session token.'); } - if (!cancelled) setToken(t); + if (!cancelled) { + tokenRef.current = t; + setToken(t); + } } catch (e) { if (!cancelled) setError(e as Error); } @@ -215,6 +233,7 @@ Update your `App.tsx` file to: 2. Pass it to the `DbConnection` builder using `.withToken(...)`. This mirrors the Auth0 flow: SpacetimeDB receives a bearer token (JWT) and can validate it server-side. +The token returned by `useClerkToken` stays stable while the user remains signed in, so Clerk's background token refreshes do not rebuild the `SpacetimeDBProvider` connection.