using better-auth to protect the entire route
I have implemented better-auth for tanstack start (https://www.better-auth.com/docs/integrations/tanstack) and I want to protect all pages in the route, how can I do that?
authClient.getSession()src/lib/authfunctions.ts file similar to dotnize's auth/functions.ts file: import { $getUser } from '@/lib/authfunctions' and grab user data in beforeLoad method using const user = await $getUser(). getRequest method in this thread and thought oh I just found you, you sneaky request object.

tried to stream query ["some_key"] after stream was already closed since you are using tanstack query in ssr? I have it all the time, which made me move away from tanstack query in beforeLoadA query (e.g., via prefetchQuery or ensureQueryData in beforeLoad/loader) starts streaming data to the client during prefetch. A mid-stream redirect (e.g., auth guard) aborts/closes the stream prematurely. Client hydration then attempts to resume/access the orphaned query state, triggering the errorauthClient.getSession()src/lib/authfunctions.tsauth/functions.tsimport { $getUser } from '@/lib/authfunctions'beforeLoadconst user = await $getUser()getRequestoh I just found you, you sneaky request objecttried to stream query ["some_key"] after stream was already closedA query (e.g., via prefetchQuery or ensureQueryData in beforeLoad/loader) starts streaming data to the client during prefetch. A mid-stream redirect (e.g., auth guard) aborts/closes the stream prematurely. Client hydration then attempts to resume/access the orphaned query state, triggering the errorexport const Route = createFileRoute("/_auth")({
component: AuthLayout,
beforeLoad: async ({ context: { user } }) => {
if (user) {
throw redirect({
to: "/",
});
}
},
}); beforeLoad: async () => {
try {
const session = await fetchSessionFn();
return session;
} catch (error) {
console.error("Failed to fetch session:", error);
// You can return a default session object or null
return null;
}
},export const fetchSessionFn = createServerFn({ method: "GET" }).handler(
async () => {
const res = await fetch(`${MERCURY_URL}/api/auth/get-session`, {
headers: {
//get the cookie from the request
cookie: getHeader("cookie") || "",
},
});
if (!res.ok) {
throw new Error("Not authenticated");
}
const session = (await res.json()) as Session;
return session;
},
);const getUser = createServerFn({ method: "GET" }).handler(async () => {
const { headers } = getWebRequest()!;
const session = await auth.api.getSession({ headers });
return session?.user || null;
});
export const Route = createRootRouteWithContext<{
queryClient: QueryClient;
user: Awaited<ReturnType<typeof getUser>>;
}>()({
beforeLoad: async ({ context }) => {
const user = await context.queryClient.fetchQuery({
queryKey: ["user"],
queryFn: ({ signal }) => getUser({ signal }),
}); // we're using react-query for caching, see router.tsx
return { user };
},import { createServerFn } from "@tanstack/react-start";
import { getRequest } from "@tanstack/react-start/server";
import { auth } from "@/lib/auth";
export const $getUser = createServerFn({ method: "GET" }).handler(async () => {
const session = await auth.api.getSession({ headers: getRequest().headers });
return session?.user || null;
});const getUserSessionFromServer = createServerFn({ method: "GET" }).handler(
async () => {
const { headers } = getRequest();
const session = await auth.api.getSession({ headers });
return session || null;
},
);
const checkAuthentication = async ({
authClientWeb,
queryClient,
}: {
authClientWeb: typeof authClient;
queryClient: QueryClient;
}) => {
let sessionDataFromServer = null;
const { data: clientSessionData } = await authClientWeb.getSession();
if (!clientSessionData?.session) {
sessionDataFromServer = await queryClient.fetchQuery({
queryKey: ["user", "session"],
queryFn: ({ signal }) => getUserSessionFromServer({ signal }),
});
}
return {
isAuthenticated: !!(
clientSessionData?.session || sessionDataFromServer?.session
),
};
};beforeLoad: async () => {
const session = await getUser();
const customerState = await getPayment();
return { session };
},export const getUserSession = createIsomorphicFn()
.server(async () => {
const { headers } = getRequest();
const session = await auth.api.getSession({
headers,
query: {
disableCookieCache: true,
},
});
return session || null;
})
.client(async () => {
const session = (await authClient.getSession()).data;
return session || null;
});
export const authQueryOptions = () =>
queryOptions({
queryKey: ["session"],
queryFn: async () => await getUserSession(),
// Match better-auth's cookie cache duration (1 minute)
// This prevents React Query from serving stale data beyond what better-auth allows
staleTime: 1000 * 60, // 1 minute - matches better-auth cookieCache.maxAge
gcTime: 0,
});
// in beforeLoad
...
beforeLoad: async ({ context }) => {
const session = await context.queryClient.ensureQueryData({
...authQueryOptions(),
revalidateIfStale: true,
});
if (!session) {
throw redirect({
to: "/$pathname",
params: { pathname: "/sign-in" },
});
}
return { session };
},
...import { getSessionCookie } from "better-auth/cookies";
const sessionCookie = getSessionCookie(request);