SSR
Customize the Solid Query client and prefetch auth data on the server with TanStack Start.
Every Better Auth UI Solid hook uses TanStack Solid Query. A shared QueryClient lets the application prefetch sessions, protect routes, and hydrate the browser cache.
The examples use TanStack Start with Solid Router. They match examples/start-solid-zaidan-example.
Other Solid applications can use the same QueryClient pattern with different router configuration.
Install the SSR integration
npm install @tanstack/solid-query @tanstack/solid-router @tanstack/solid-start solid-jsTanStack Start connects Solid Router to server and browser rendering. Solid Query provides the shared QueryClient.
Pass this client through the route context and AuthProvider. Better Auth UI hooks can then read data that route loaders prefetched.
Customize the QueryClient
Create the QueryClient inside the router factory. Each SSR request then receives a separate cache.
Apply defaultOptions in this factory. The example uses a staleTime of 5 seconds to reuse recent results during navigation.
import { QueryClient } from "@tanstack/solid-query"
import { createRouter } from "@tanstack/solid-router"
import { routeTree } from "./routeTree.gen"
export const getRouter = () => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5000
}
}
})
return createRouter({
context: { queryClient },
defaultPreloadStaleTime: 0,
routeTree,
scrollRestoration: true
})
}The options have these effects:
defaultPreloadStaleTime: 0tells TanStack Router to start loaders during each preload. QuerystaleTimecan still reuse cached data.context: { queryClient }exposes the client to every loader/beforeLoadhook viacontext.queryClient.- Configure
defaultOptionsas you do in other TanStack Query applications.
Type the root route context
Use createRootRouteWithContext so child routes can read the typed context.queryClient. Then pass this client to the application providers.
import type { QueryClient } from "@tanstack/solid-query"
import { createRootRouteWithContext, Outlet } from "@tanstack/solid-router"
import { Providers } from "@/components/providers"
export const Route = createRootRouteWithContext<{
queryClient: QueryClient
}>()({
component: RootComponent
})
function RootComponent() {
const routeContext = Route.useRouteContext()
return (
<Providers queryClient={routeContext().queryClient}>
<Outlet />
</Providers>
)
}The tree of routes now has { queryClient } available in every loader, beforeLoad, and component via Route.useRouteContext().
Prefetch the session in beforeLoad
Each Solid query provides matching ensure*, prefetch*, and fetch* helpers. Base helpers come from @better-auth-ui/solid or @better-auth-ui/core.
Optional plugin helpers come from @better-auth-ui/core/plugins/<plugin>. These helpers accept QueryClient, authClient, and the query parameters.
| Helper | When to use |
|---|---|
ensureSession | Read the session, resolving from cache if fresh. Most common in loaders. |
prefetchSession | Kick off a background fetch without awaiting. Good for soft preloads. |
fetchSession | Fetch through the query client, reusing fresh cached data according to Query stale-time rules. |
Use ensureSession in beforeLoad to protect a route. Connect the browser and server helpers with createIsomorphicFn.
The server path receives the request headers. The browser path reuses authClient.
import { viewPaths } from "@better-auth-ui/core"
import { ensureSession } from "@better-auth-ui/core"
import { ensureSessionServer } from "@better-auth-ui/core/server"
import { createFileRoute, notFound, redirect } from "@tanstack/solid-router"
import { createIsomorphicFn } from "@tanstack/solid-start"
import { getRequestHeaders } from "@tanstack/solid-start/server"
import { Settings } from "@/components/auth/settings/settings"
import { auth } from "@/lib/auth"
import { authClient } from "@/lib/auth-client"
export const Route = createFileRoute("/settings/$path")({
async beforeLoad({ params: { path }, context: { queryClient }, location }) {
if (!Object.values(viewPaths.settings).includes(path)) {
throw notFound()
}
const ensureSessionIso = createIsomorphicFn()
.server(() =>
ensureSessionServer(queryClient, auth, {
headers: getRequestHeaders()
})
)
.client(() => ensureSession(queryClient, authClient))
const session = await ensureSessionIso()
if (!session) {
throw redirect({
to: "/auth/$path",
params: { path: "sign-in" },
search: { redirectTo: location.href }
})
}
return { session }
},
component: SettingsPage
})
function SettingsPage() {
const path = () => Route.useParams()().path
return <Settings path={path()} />
}The loader adds the session to the query cache before the protected route renders. Settings and its useSession calls can read it immediately.
TanStack Router adds the value from beforeLoad to the route context. Read this value in a component with Route.useRouteContext().
This pattern avoids another query for the same session data.
Prefetch without blocking
Use prefetchSession to fill the cache without blocking navigation. This pattern can prepare a public route that later shows an authenticated widget:
import { prefetchSession } from "@better-auth-ui/core"
export const Route = createFileRoute("/")({
loader: ({ context: { queryClient } }) => {
void prefetchSession(queryClient, authClient)
}
})The same pattern works for other Solid query helpers. Base helpers such as prefetchListAccounts come from the Solid or core packages.
Plugin helpers come from their core plugin packages. To call the Better Auth server directly, use the core or plugin /server entrypoint.
Server-only helpers
To avoid an HTTP request, import the session helpers from @better-auth-ui/core/server. Call them from a server function or another server runtime.
These helpers accept the Better Auth server instance instead of authClient:
import { ensureSessionServer } from "@better-auth-ui/core/server"
import type { QueryClient } from "@tanstack/solid-query"
import { createServerFn } from "@tanstack/solid-start"
import { getRequestHeaders } from "@tanstack/solid-start/server"
import { auth } from "@/lib/auth"
const getSession = createServerFn().handler(() =>
auth.api.getSession({ headers: getRequestHeaders() })
)
export const ensureServerSession = (queryClient: QueryClient) =>
ensureSessionServer(queryClient, auth, { headers: getRequestHeaders() })Session server-auth helpers are canonical from @better-auth-ui/core/server as sessionOptionsServer, ensureSessionServer, prefetchSessionServer, and fetchSessionServer.
Available @better-auth-ui/core/server exports include:
- Session helpers:
sessionOptionsServer,ensureSessionServer,prefetchSessionServer,fetchSessionServer - Base server auth type:
AuthServer - Settings helpers:
listAccountsOptions,ensureListAccounts,prefetchListAccounts,fetchListAccounts,accountInfoOptions,ensureAccountInfo,listSessionsOptions, andensureListSessionswith matching prefetch/fetch helpers
Plugin server query helper families live under their plugin server entrypoints:
- API-key (
@better-auth-ui/core/plugins/api-key/server):listApiKeysOptions,ensureListApiKeys,prefetchListApiKeys,fetchListApiKeys - Multi-session (
@better-auth-ui/core/plugins/multi-session/server):listDeviceSessionsOptions,ensureListDeviceSessions,prefetchListDeviceSessions,fetchListDeviceSessions - Passkey (
@better-auth-ui/core/plugins/passkey/server):listPasskeysOptions,ensureListPasskeys,prefetchListPasskeys,fetchListPasskeys - Organization (
@better-auth-ui/core/plugins/organization/server):activeOrganizationOptions,ensureActiveOrganization,prefetchActiveOrganization,fetchActiveOrganization,fullOrganizationOptions,ensureFullOrganization,listOrganizationsOptions,ensureListOrganizations,listOrganizationMembersOptions,ensureListOrganizationMembers,listOrganizationInvitationsOptions,ensureListOrganizationInvitations,listUserInvitationsOptions,ensureListUserInvitations,hasPermissionOptions, andensureHasPermissionwith matching prefetch/fetch helpers
Server helpers, loader helpers, and component hooks use the same cache keys. Browser hooks can therefore read data that route loaders prefetched.
These helpers do not own secrets, routes, copied interface code, or registry installation. The Solid package also does not create routes.
For TanStack Start installation and component customization, use the Zaidan documentation.
Last updated on