Execution Model
All code in TanStack Start is isomorphic by default — it runs on both server and client unless explicitly constrained. This page demonstrates each execution boundary.
| API | Runs on | If called from wrong env |
|---|---|---|
| createServerFn() | Server | Network request (RPC) |
| createServerOnlyFn() | Server | Throws error |
| createClientOnlyFn() | Client | Throws error |
| createIsomorphicFn() | Both | Picks correct impl |
| <ClientOnly> | Client | Renders fallback |
| useHydrated() | Both | Returns false on server |
createServerFn (RPC)
createServerFn({ method: 'GET' }).handler(async () => { ... })Runs on the server. When called from the client, it becomes a network request automatically. Use for database access, secrets, and mutations.
Cloudflare Workers2026-02-20T10:09:13.889ZedgecreateServerOnlyFn (hard boundary)
createServerOnlyFn(() => process.env.SECRET)Unlike createServerFn, this has no RPC bridge. Calling it from the client throws an error immediately. Use for utility functions that must never leave the server — secret access, file system reads, etc.
// ✅ Safe — only callable from server code
const getDbUrl = createServerOnlyFn(() => process.env.DATABASE_URL)
// ❌ Calling from client crashes at runtime
const url = getDbUrl() // throws Error
When to choose this over createServerFn: Use createServerOnlyFn for pure utility functions that return secrets or do file I/O. Use createServerFn when you need the client to be able to call the function (it creates an RPC endpoint).
createClientOnlyFn (hard boundary)
createClientOnlyFn(() => ({ width: window.innerWidth, ... }))The inverse of createServerOnlyFn. Calling it on the server throws an error. Use for browser API wrappers — localStorage, DOM measurements, geolocation, etc.
Waiting for client...
This function is only called inside useEffect so it never runs during SSR. Resize your browser to see it update.
Loader + Server Function
loader: () => getServerInfo()Loaders are isomorphic — they run on both server (SSR) and client (navigation). Never access secrets directly in a loader. Delegate to a server function instead.
// ❌ loader runs on client too — secret leaks into bundle
loader: () => fetch(`/api?key=${process.env.SECRET}`)
// ✅ server function keeps secrets server-side
loader: () => getServerInfo()
createIsomorphicFn
createIsomorphicFn().server(() => ...).client(() => ...)Provides different implementations depending on the environment. The framework picks the right one at runtime. Good for logging, storage adapters, and feature detection.
serverDuring SSR this shows "server". After hydration it shows "client". Unlike the hard boundary functions, this never throws — it always has an implementation for the current environment.
<ClientOnly> component
<ClientOnly fallback={...}>...</ClientOnly>Renders children only after hydration. During SSR, renders the fallback. Use when an entire component tree needs browser APIs.
Waiting for hydration...
<ClientOnly> vs createClientOnlyFn: Use the component when you need to guard an entire subtree of JSX. Use the function when you need a callable utility.
useHydrated
const hydrated = useHydrated()Returns false during SSR and first client render, then true after hydration. Useful for conditional rendering that avoids hydration mismatches.
falseLoading...The clock uses useEffect to avoid a hydration mismatch — the server and client would render different times otherwise.
Progressive Enhancement
Works without JS, enhanced with JSBuild forms that work as plain HTML, then enhance with client-side behavior. The form submits via GET without JavaScript; with JavaScript it can do instant search.