
Tanstack Integration Best Practices
Configure TanStack Router and TanStack Query so one cache owns server data and loaders stay predictable in solo-built SPAs.
Overview
Tanstack Integration Best Practices is an agent skill for the Build phase that teaches solo builders to make TanStack Query the single caching authority when paired with TanStack Router.
Install
npx skills add https://github.com/deckardger/tanstack-agent-skills --skill tanstack-integration-best-practicesWhat is this skill?
- Sets `defaultPreloadStaleTime: 0` on the router when Query is authoritative
- Shows bad pattern: parallel Router loader fetch plus duplicate `useQuery` keys
- Documents `setupRouterSsrQueryIntegration` with a shared `QueryClient`
- Recommends explicit `staleTime` and `refetchOnWindowFocus` defaults on queries
- Single-source-of-truth rule: MEDIUM priority cache consolidation
Adoption & trust: 1.6k installs on skills.sh; 180 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You combined TanStack Router loaders with TanStack Query and now the same posts list can be fresh in one cache and stale in another.
Who is it for?
Indie devs standardizing data loading in TanStack Start/Router apps before the route tree grows messy.
Skip if: Teams on Remix-only or Next.js App Router data APIs with no TanStack Query in the stack.
When should I use this skill?
User is integrating TanStack Router loaders with TanStack Query or seeing inconsistent data after navigation.
What do I get? / Deliverables
After applying the skill, router preloading defers to Query with aligned keys and defaults so navigation and refetch behavior stay consistent.
- Router factory with `defaultPreloadStaleTime: 0`
- Loader/query key alignment pattern
- Documented default query options
Recommended Skills
Journey fit
Canonical shelf is Build because the guidance applies while wiring routing, loaders, and client data layers—not during launch SEO or ops monitoring. Frontend subphase fits Router file routes, `useQuery`, and SSR query integration patterns that shape page architecture.
How it compares
Procedural integration guidance—not a generic React performance linter or an MCP server.
Common Questions / FAQ
Who is tanstack-integration-best-practices for?
Solo and indie builders using TanStack Router file routes with TanStack Query who want one clear cache owner.
When should I use tanstack-integration-best-practices?
During Build/frontend when creating routers, SSR query integration, or fixing duplicate fetches on route transitions.
Is tanstack-integration-best-practices safe to install?
It is documentation-style guidance; review the Security Audits panel on this Prism page before trusting any third-party skill package.
SKILL.md
READMESKILL.md - Tanstack Integration Best Practices
# cache-single-source: Let TanStack Query Manage Caching ## Priority: MEDIUM ## Explanation When using TanStack Router with TanStack Query, let Query be the single source of truth for caching. Disable Router's built-in cache with `defaultPreloadStaleTime: 0` to avoid confusion about which cache is authoritative. ## Bad Example ```tsx // Both Router and Query caching enabled - confusing const router = createRouter({ routeTree, context: { queryClient }, // Default router caching enabled // defaultPreloadStaleTime: 30000 (default) }) export const Route = createFileRoute('/posts')({ loader: async () => { // Fetches directly - cached by Router const posts = await fetchPosts() return { posts } }, component: PostsPage, }) function PostsPage() { // Also uses Query cache - which is authoritative? const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }) // Now there are TWO caches with potentially different data } ``` ## Good Example ```tsx // router.tsx - Disable router cache when using Query import { QueryClient } from '@tanstack/react-query' import { createRouter } from '@tanstack/react-router' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' import { routeTree } from './routeTree.gen' export function getRouter() { const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 2, // 2 minutes refetchOnWindowFocus: false, }, }, }) const router = createRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent', defaultPreloadStaleTime: 0, // Let Query manage caching scrollRestoration: true, }) setupRouterSsrQueryIntegration({ router, queryClient, }) return router } // routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: async ({ context: { queryClient } }) => { // Query is the single cache source await queryClient.ensureQueryData(postQueries.all()) // No return needed - data lives in Query cache }, component: PostsPage, }) function PostsPage() { // Single source of truth const { data: posts } = useSuspenseQuery(postQueries.all()) return <PostList posts={posts} /> } ``` ## Cache Comparison | Feature | Router Cache | Query Cache | |---------|-------------|-------------| | Invalidation | Manual/time-based | Query keys, patterns | | Background refetch | No | Yes | | Optimistic updates | No | Yes | | Mutations | No built-in | Full support | | DevTools | Limited | Rich debugging | | Cross-route sharing | Full | Full | ## Good Example: Coordinated Caching Config ```tsx // router.tsx export function getRouter() { const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // Fresh for 1 minute gcTime: 10 * 60 * 1000, // Cache for 10 minutes refetchOnWindowFocus: true, // Refetch when tab focused retry: 1, }, }, }) const router = createRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent', defaultPreloadStaleTime: 0, // Router defers to Query scrollRestoration: true, defaultStructuralSharing: true, }) setupRouterSsrQueryIntegration({ router, queryClient, }) return router } ``` ## Good Example: Preload Still Works ```tsx // Preloading still works - it just uses Query's cache export function getRouter() { const queryClient = new QueryClient() const router = createRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent', // Preload on hover defaultPreloadStaleTime: 0, // Query decides if data is stale }) setupRouterSsrQueryIntegration({ router, queryClient }) return router } // When user hovers a Link: // 1. Router triggers preload // 2. Loader runs ensureQueryData // 3. Query checks its cache - fresh? skip fetch. stale? refetch. // 4. User clicks - data already in Query ca