Data Loading
Load data in TanStack Start routes with server-side query warming and client-side suspense.
VitNode loads data through two primary mechanisms:
- Plugin Routes: Loaded through
definePluginRoute({ load })with isomorphic data passing. - Host App Routes: Warmed in the route
loadervia TanStack Query and read in components viauseSuspenseQuery.
Quick start
1. In a Plugin Route (Recommended)
Plugins load data using definePluginRoute. The loader runs during SSR and client navigation, handing typed loaderData to the page component:
import type { PluginRoutePageProps } from "@vitnode/core/routing"
import { definePluginRoute } from "@vitnode/core/routing"
interface Announcement {
id: string
title: string
}
export const route = definePluginRoute({
load: async ({ context }) => {
return await fetchAnnouncements(context.locale)
},
})
const AnnouncementsPage = ({ loaderData }: PluginRoutePageProps<Announcement[]>) => (
<div className="container mx-auto p-4 flex flex-col gap-4">
{loaderData.map((item) => (
<h2 key={item.id} className="text-xl font-semibold">
{item.title}
</h2>
))}
</div>
)
export default AnnouncementsPage2. In an Application Route File
For application-owned routes, warm TanStack Query in the route loader and consume it with useSuspenseQuery:
Step 1: Define the Query
import { queryOptions } from "@tanstack/react-query"
import { RECORD_STALE_TIME } from "@vitnode/core/lib/query-freshness"
export const announcementsQueryKey = () => ["announcements"] as const
export const announcementsQuery = () =>
queryOptions({
queryKey: announcementsQueryKey(),
queryFn: async () => await fetchAnnouncements(),
staleTime: RECORD_STALE_TIME,
})Step 2: Warm in Route Loader
import { createFileRoute } from "@tanstack/react-router"
import { announcementsQuery } from "#/features/announcements/query"
export const Route = createFileRoute("/_main/announcements")({
loader: async ({ context }) =>
await context.queryClient.ensureQueryData({
...announcementsQuery(),
revalidateIfStale: true,
}),
component: AnnouncementsPage,
})Step 3: Consume in Component
import { useSuspenseQuery } from "@tanstack/react-query"
import { announcementsQuery } from "./query"
export const AnnouncementsPage = () => {
const { data } = useSuspenseQuery(announcementsQuery())
return (
<div className="p-4">
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
)
}Invalidating After Mutations
After creating, editing, or deleting a record, invalidate the query key so TanStack Query refetches fresh data:
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { toast } from "sonner"
import { announcementsQueryKey } from "./query"
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: createAnnouncement,
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: announcementsQueryKey() })
toast.success("Announcement published!")
},
})Freshness Constants
VitNode provides standard stale times in @vitnode/core/lib/query-freshness:
| Constant | Duration | Use Case |
|---|---|---|
RECORD_STALE_TIME | 30 seconds | Fast-changing user feeds, announcements |
STATIC_STALE_TIME | 5 minutes | Site configuration, navigation, permissions |