Fetcher
Call your Hono API with end-to-end type safety.
Use this by default
In a TanStack Start app, use fetcher through a plugin API client. The same
request works during SSR and browser navigation.
During SSR, VitNode forwards the visitor’s request to the API. In the browser,
it calls /api/* directly. You do not need to write createIsomorphicFn() or
choose a transport.
Create your API client once
Define it in your plugin
Keep this in one plugin file. Features import notesApi; they never set up a
module reference themselves.
import type { notesModule } from "../api/notes.module"
import { createApiClient } from "@vitnode/core/tanstack/fetcher"
export const notesApi = createApiClient<typeof notesModule>("@acme/site-notes")Fetch data
import { queryOptions } from "@tanstack/react-query"
import { notesApi } from "../../api/client"
export const notesQueryKey = ["@acme/site-notes", "notes"] as const
export const notesQuery = () =>
queryOptions({
queryKey: notesQueryKey,
queryFn: async ({ signal }) => {
const response = await notesApi.fetch({
method: "get",
module: "notes",
options: { signal },
path: "/",
})
if (!response.ok) {
throw new Error(`The notes API answered ${response.status}.`)
}
return await response.json()
},
})Use it on a page or in a mutation
Warm the query in the route loader. The component reads that same cache entry
with useQuery(notesQuery()).
import { definePluginRoute } from "@vitnode/core/routing"
import { notesQuery } from "../features/notes/notes-query"
export const route = definePluginRoute({
load: async ({ context }) =>
await context.queryClient.ensureQueryData(notesQuery()),
})Use the same API client, then invalidate the data that changed.
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { notesApi } from "../../api/client"
import { notesQueryKey } from "./notes-query"
export const useCreateNote = () => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (title: string) => {
const response = await notesApi.fetch({
args: { body: { title } },
method: "post",
module: "notes",
path: "/",
})
if (!response.ok) throw new Error("Could not create the note.")
return await response.json()
},
onSuccess: async () =>
await queryClient.invalidateQueries({ queryKey: notesQueryKey }),
})
}Server-only work
Use @vitnode/core/tanstack/fetcher/server only for a server function, cookie
relay, cron/job, secret, or a custom API origin.
Keep it server-only
Put code that imports this fetcher in a *.server.ts file, or call it only
from a server function.
What the types do
method,module, andpathare always required.argsis required when the route declares a body, params, or query.- TypeScript infers the valid route, arguments, response status, and JSON body.
Generated Content Engine modules have no static module type, so use
rawFetcher for them instead.