Files
dotfiles/dot_agents/skills/tanstack-integration-best-practices/rules/setup-query-client-context.md
T
Prad Nukala 6406208b35 shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables:
  ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone
  ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj
  (ghg-clone not named ghg: unrelated cargo binary owns that name)
- cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho
  cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files
- fish keeps only shell-state functions; converted ones now hit PATH
- cp-appid ignore gating moved to .local/bin path
- dot_agents/skills: 82 skills materialized from dotstate storage so
  dot_claude skill symlinks resolve on fresh bootstrap; dotstate
  profile/symlinks.json released .agents/skills .bashrc .zshrc
2026-08-10 15:15:06 -04:00

4.3 KiB

setup-query-client-context: Pass QueryClient Through Router Context

Priority: CRITICAL

Explanation

Pass the QueryClient instance through TanStack Router's context system rather than using a global. This enables proper SSR with per-request clients, testability, and type-safe access in loaders. Use @tanstack/react-router-ssr-query for automatic SSR integration.

Bad Example

// lib/query-client.ts - Global singleton
export const queryClient = new QueryClient()

// routes/posts.tsx - Importing global
import { queryClient } from '@/lib/query-client'

export const Route = createFileRoute('/posts')({
  loader: async () => {
    // Using global - breaks SSR, harder to test
    return queryClient.fetchQuery(postQueries.list())
  },
})

Good Example: Modern Router Setup

// routes/__root.tsx
import { createRootRouteWithContext } from '@tanstack/react-router'
import { QueryClient } from '@tanstack/react-query'

interface RouterContext {
  queryClient: QueryClient
}

export const Route = createRootRouteWithContext<RouterContext>()({
  component: RootComponent,
})

// router.tsx
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: {
        refetchOnWindowFocus: false,
        staleTime: 1000 * 60 * 2, // 2 minutes
      },
    },
  })

  const router = createRouter({
    routeTree,
    context: { queryClient },
    defaultPreload: 'intent',
    defaultPreloadStaleTime: 0,
    scrollRestoration: true,
  })

  setupRouterSsrQueryIntegration({
    router,
    queryClient,
  })

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof getRouter>
  }
}

// routes/posts.tsx - Access from context
export const Route = createFileRoute('/posts')({
  loader: async ({ context: { queryClient } }) => {
    // Type-safe access to queryClient from context
    await queryClient.ensureQueryData(postQueries.list())
  },
})

Good Example: Root Route with Context

// routes/__root.tsx
import { createRootRouteWithContext, Outlet, HeadContent, Scripts } from '@tanstack/react-router'
import { QueryClient } from '@tanstack/react-query'

interface RouterContext {
  queryClient: QueryClient
  user: User | null
}

export const Route = createRootRouteWithContext<RouterContext>()({
  component: RootComponent,
  beforeLoad: async ({ context }) => {
    // Prefetch auth or other global data
    await context.queryClient.ensureQueryData(authQueryOptions)
  },
})

function RootComponent() {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  )
}

TanStack Start handles SSR and hydration automatically via the Vite plugin. No separate entry files needed.

Good Example: Testing with Mock QueryClient

// tests/posts.test.tsx
import { createRouter, RouterProvider } from '@tanstack/react-router'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render } from '@testing-library/react'

function renderWithProviders(route: string) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
    },
  })

  const router = createRouter({
    routeTree,
    context: { queryClient },
    Wrap: ({ children }) => (
      <QueryClientProvider client={queryClient}>
        {children}
      </QueryClientProvider>
    ),
  })

  return {
    ...render(<RouterProvider router={router} />),
    queryClient,
  }
}

test('loads posts', async () => {
  const { queryClient } = renderWithProviders('/posts')

  // Pre-populate cache for testing
  queryClient.setQueryData(['posts'], mockPosts)

  // ... assertions
})

Context

  • Router context flows to all loaders and beforeLoad hooks
  • Creating QueryClient per request is essential for SSR
  • Use setupRouterSsrQueryIntegration for automatic SSR handling
  • Access queryClient via context parameter in loaders
  • This pattern enables clean dependency injection for testing
  • Install: npm install @tanstack/react-router-ssr-query