Server Functions & Isomorphic Fetching

When to use TanStack Start server functions, createIsomorphicFn, and Hono API routes in VitNode.

VitNode separates backend business logic into Hono API routes while using createIsomorphicFn for data fetching across SSR and client-side navigation.

Decision Matrix

GoalRecommended ToolRationale
Route Data FetchingcreateIsomorphicFnSSR fetches directly on server; client fetches via fetcherClient.
API Endpoints & MutationsHono API routesEnforces staff permissions, validation schemas, and database transactions.
Cookie Minting on HostcreateServerFn (App only)Only code inside the host request can set response headers directly.
Plugin Server CodeHono API modulesPlugins must never declare createServerFn (uncompiled handlers resolve to undefined).

No createServerFn in Plugins

A plugin package may declare createIsomorphicFn, but never createServerFn. Server functions belong exclusively to the host application.


Isomorphic Data Fetching (createIsomorphicFn)

TanStack Router loaders run on the server during the initial paint, and on the client for subsequent navigations. createIsomorphicFn bridges both environments seamlessly:

src/features/devices/fetcher.ts
import { createIsomorphicFn } from "@tanstack/react-start"
import { clientModule, fetcherClient } from "@vitnode/core/lib/fetcher-client"
import { fetcher } from "@vitnode/core/tanstack/fetcher/server"
import type { usersModule } from "@vitnode/core/api/modules/users/users.module"

const moduleRef = clientModule<typeof usersModule>("@vitnode/core")

export const fetchDevices = createIsomorphicFn()
  // Server execution (SSR)
  .server(async () => {
    const res = await fetcher(usersModule, {
      method: "get",
      module: "users",
      path: "/devices",
    })
    return await res.json()
  })
  // Client execution (SPA navigation)
  .client(async () => {
    const res = await fetcherClient(moduleRef, {
      method: "get",
      module: "users",
      path: "/devices",
    })
    return await res.json()
  })

Consume fetchDevices directly in your route loader:

apps/web/src/routes/_main/devices.tsx
export const Route = createFileRoute("/_main/devices")({
  loader: async () => await fetchDevices(),
  component: DevicesPage,
})

When to use createServerFn (Host App Only)

Use createServerFn only when your host application needs to modify response cookies directly:

apps/web/src/features/auth/server-fn.ts
import { createServerFn } from "@tanstack/react-start"
import { setCookie } from "vinxi/http"

export const setSessionTheme = createServerFn({ method: "POST" })
  .validator((theme: string) => theme)
  .handler(async ({ data }) => {
    setCookie("theme", data, { path: "/", httpOnly: true })
    return { success: true }
  })

Learn More