### Good Example: Modern Router Setup with Context Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-integration/rules/setup-query-client-context.md Illustrates the recommended modern approach for setting up the router and QueryClient using context for SSR integration and type-safe access. ```tsx // routes/__root.tsx import { createRootRouteWithContext } from '@tanstack/react-router' import { QueryClient } from '@tanstack/react-query' interface RouterContext { queryClient: QueryClient } export const Route = createRootRouteWithContext()({ component: RootComponent, }) ``` ```tsx // 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 } } ``` ```tsx // 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: Full Authentication Flow with TanStack Start Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/auth-session-management.md This example demonstrates a complete authentication flow including login, logout, and fetching the current user using server functions. It ensures secure password handling and session management. ```tsx // lib/auth.functions.ts import { createServerFn } from '@tanstack/react-start' import { redirect } from '@tanstack/react-router' import { getSession } from './session.server' import { hashPassword, verifyPassword } from './password.server' // Login export const login = createServerFn({ method: 'POST' }) .validator(z.object({ email: z.string().email(), password: z.string().min(1), })) .handler(async ({ data }) => { const user = await db.users.findUnique({ where: { email: data.email }, }) if (!user || !await verifyPassword(data.password, user.passwordHash)) { throw new Error('Invalid email or password') } const session = await getSession() await session.update({ userId: user.id, email: user.email, }) throw redirect({ to: '/dashboard' }) }) // Logout export const logout = createServerFn({ method: 'POST' }) .handler(async () => { const session = await getSession() await session.clear() throw redirect({ to: '/' }) }) // Get current user export const getCurrentUser = createServerFn() .handler(async () => { const session = await getSession() const data = await session.data if (!data?.userId) { return null } const user = await db.users.findUnique({ where: { id: data.userId }, select: { id: true, email: true, name: true, avatar: true, // Don't include passwordHash! }, }) return user }) ``` -------------------------------- ### Good Example: Manual SSR Setup Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/ssr-dehydration.md This example illustrates manual SSR setup for TanStack Query, involving server-side prefetching, dehydrating the cache, and client-side hydration. It emphasizes using a safe serializer and setting `staleTime`. ```tsx // server.tsx import { dehydrate, QueryClient } from '@tanstack/react-query' import { renderToString } from 'react-dom/server' export async function render(url: string) { const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // Prevent immediate client refetch }, }, }) // Prefetch required data await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: fetchPosts, }) const dehydratedState = dehydrate(queryClient) const html = renderToString( ) // Serialize safely - JSON.stringify is XSS vulnerable const serializedState = serialize(dehydratedState) return `
${html}
` } // client.tsx import { hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query' const queryClient = new QueryClient() hydrate(queryClient, window.__DEHYDRATED_STATE__) hydrateRoot( document.getElementById('app'), ) ``` -------------------------------- ### New TanStack Query Setup Steps Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/CLAUDE.md Provides a step-by-step guide for setting up TanStack Query, including loading the relevant skill and applying specific rules for query organization, caching, and error handling. ```text 1. Load tanstack-query skill 2. Apply `qk-factory-pattern` for query organization 3. Apply `cache-stale-time` and `cache-gc-time` 4. Set up error boundaries per `err-error-boundaries` ``` -------------------------------- ### Good Example: Using Separated Code in Components Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/file-separation.md Shows how to safely import and use server functions and shared utilities within client components, leveraging TanStack Start's RPC capabilities. ```tsx // components/PostList.tsx import { getPosts } from '@/lib/posts.functions' // Safe - RPC stub on client import { formatPostDate } from '@/lib/posts' // Safe - shared utility import type { Post } from '@/lib/posts' // Safe - type only function PostList() { const postsQuery = useQuery({ queryKey: ['posts'], queryFn: () => getPosts(), // Calls server function }) return ( ) } ``` -------------------------------- ### Good Example: createServerFn for GET Data Fetching Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/sf-create-server-fn.md Illustrates using createServerFn for fetching lists of posts and a single post by ID, including validation for the single post fetch. ```tsx // lib/posts.functions.ts export const getPosts = createServerFn() // GET is default .handler(async () => { const posts = await db.posts.findMany({ orderBy: { createdAt: 'desc' }, take: 20, }) return posts }) export const getPost = createServerFn() .validator(z.object({ id: z.string() })) .handler(async ({ data }) => { const post = await db.posts.findUnique({ where: { id: data.id }, }) if (!post) { throw notFound() } return post }) // Usage in route loader export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { return await getPost({ data: { id: params.postId } }) }, }) ``` -------------------------------- ### Good Example: Link with Preloading Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/nav-link-component.md This example shows how to configure preloading for links using the `preload` and `preloadDelay` props on the `` component. This can improve perceived performance by preloading linked routes on hover or focus. ```tsx import { Link } from '@tanstack/react-router' function PostList({ posts }: { posts: Post[] }) { return ( ) } ``` -------------------------------- ### Enable Intent-Based Preloading Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/preload-intent.md This example configures the router to use `'intent'` preloading by default, with a small delay. Links will automatically start preloading data when hovered or focused, leading to instant navigation upon clicking. ```tsx // router.tsx - Enable preloading by default const router = createRouter({ routeTree, defaultPreload: 'intent', // Preload on hover/focus defaultPreloadDelay: 50, // Wait 50ms before starting }) declare module '@tanstack/react-router' { interface Register { router: typeof router } } // Links automatically preload on hover function PostList({ posts }: { posts: Post[] }) { return ( ) } ``` -------------------------------- ### Good Example: Comprehensive and Targeted Invalidation Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/mut-invalidate-queries.md Illustrates effective mutation setups with `onSuccess` handlers that comprehensively invalidate all potentially affected queries, ensuring data freshness. Includes examples for creating, updating, and cross-entity invalidations. ```tsx // Comprehensive invalidation const createTodo = useMutation({ mutationFn: (newTodo) => api.createTodo(newTodo), onSuccess: () => { // Invalidate all todo-related queries queryClient.invalidateQueries({ queryKey: ['todos'] }) }, }) // Targeted invalidation with all affected queries const updateTodo = useMutation({ mutationFn: ({ id, data }) => api.updateTodo(id, data), onSuccess: (data, { id }) => { // Specific todo queryClient.invalidateQueries({ queryKey: ['todos', id] }) // Lists that might contain this todo queryClient.invalidateQueries({ queryKey: ['todos', 'list'] }) // If todo status changed, invalidate filtered views queryClient.invalidateQueries({ queryKey: ['todos', 'completed'] }) queryClient.invalidateQueries({ queryKey: ['todos', 'active'] }) }, }) // Cross-entity invalidation const assignTodoToUser = useMutation({ mutationFn: ({ todoId, userId }) => api.assignTodo(todoId, userId), onSuccess: (data, { todoId, userId }) => { // Invalidate the todo queryClient.invalidateQueries({ queryKey: ['todos', todoId] }) // Invalidate user's assigned todos queryClient.invalidateQueries({ queryKey: ['users', userId, 'todos'] }) // Invalidate previous assignee's list if available if (data.previousAssignee) { queryClient.invalidateQueries({ queryKey: ['users', data.previousAssignee, 'todos'], }) } }, }) ``` -------------------------------- ### Bad Example: Only Query in Component Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-integration/rules/flow-loader-query-pattern.md This example shows a component that only uses `useQuery`, leading to a loading waterfall. It also shows a loader that fetches data but doesn't manage caching. ```tsx // Only using Query in component - loading waterfall function PostsPage() { const { data, isLoading } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }) if (isLoading) return return } // Only using loader - no cache management export const Route = createFileRoute('/posts')({ loader: async () => { const posts = await fetchPosts() // Not cached return { posts } }, }) ``` -------------------------------- ### Good Example: Link with Search Params Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/nav-link-component.md This snippet shows how to use the `` component with search parameters. It includes examples for setting specific search params and for updating existing search params using a function. ```tsx import { Link } from '@tanstack/react-router' function FilteredLink() { return ( View Electronics ) } // Preserving existing search params function SortLink({ sort }: { sort: 'asc' | 'desc' }) { return ( ({ ...prev, sort })} > Sort {sort === 'asc' ? 'Ascending' : 'Descending'} ) } ``` -------------------------------- ### No Prefetching Example Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/pf-intent-prefetch.md This example demonstrates a basic React component that fetches data only when a link is clicked, leading to a loading delay for the user. ```tsx // No prefetching - data fetches on click function PostList({ posts }: { posts: Post[] }) { return (
    {posts.map(post => (
  • {post.title} {/* User clicks, waits for data to load */}
  • ))}
) } ``` -------------------------------- ### Rule File Structure Example Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/CLAUDE.md Demonstrates the standard structure for a rule file, including the rule ID, priority, explanation, bad example, good example, and context. This format ensures clarity and consistency in rule definitions. ```markdown # rule-id: Rule Title ## Priority: CRITICAL | HIGH | MEDIUM | LOW ## Explanation Why this pattern matters and what problem it solves. ## Bad Example ```tsx // Code showing the anti-pattern // With comments explaining why it's bad ``` ## Good Example ```tsx // Code showing the recommended pattern // With comments explaining the approach ``` ## Context - When to apply this rule - When to skip this rule - Related considerations ``` -------------------------------- ### Middleware Execution Order Example Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/mw-request-middleware.md Illustrates the execution flow of middleware, showing how middleware wraps the handler. The `loggingMiddleware` example demonstrates logging before and after the `next()` call, which represents the subsequent middleware or handler. ```tsx // Example with timing: loggingMiddleware.server(async ({ next }) => { console.log('Before handler') const result = await next() // Calls next middleware/handler console.log('After handler') return result }) ``` -------------------------------- ### Good Example: Composing Server Functions Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/sf-create-server-fn.md Demonstrates how to compose multiple server functions together, such as fetching a post and its comments concurrently. ```tsx // Compose server functions export const getPostWithComments = createServerFn() .inputValidator(z.object({ postId: z.string() })) .handler(async ({ data }) => { const [post, comments] = await Promise.all([ getPost({ data: { id: data.postId } }), getComments({ data: { postId: data.postId } }), ]) return { post, comments } }) ``` -------------------------------- ### Good Example: TanStack Start/Router SSR Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/ssr-dehydration.md This example shows SSR with TanStack Query using TanStack Router. Queries are prefetched within the route loader, and the component uses `useSuspenseQuery` to access the data. ```tsx // routes/posts.tsx import { createFileRoute } from '@tanstack/react-router' import { postQueries } from '@/lib/queries' export const Route = createFileRoute('/posts')({ loader: async ({ context: { queryClient } }) => { // Prefetch in route loader await queryClient.ensureQueryData(postQueries.list()) }, component: PostsPage, }) function PostsPage() { const { data: posts } = useSuspenseQuery(postQueries.list()) return } ``` -------------------------------- ### Good Example: Static Prerendering Configuration Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/ssr-prerender.md Configure static prerendering by specifying routes to prerender at build time or enabling crawl links from the root in `app.config.ts`. The `routes/about.tsx` example shows a loader that runs at build time, not request time. ```tsx // app.config.ts import { defineConfig } from '@tanstack/react-start/config' export default defineConfig({ server: { prerender: { // Routes to prerender at build time routes: [ '/', '/about', '/contact', '/pricing', ], // Or crawl from root crawlLinks: true, }, }, }) // routes/about.tsx - Will be prerendered export const Route = createFileRoute('/about')({ loader: async () => { // Runs at BUILD time, not request time const content = await fetchAboutPageContent() return { content } }, component: AboutPage, }) ``` -------------------------------- ### Good Example: Using Link Component Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/nav-link-component.md This example demonstrates the correct usage of the `` component for navigation. It renders a proper `` tag with an `href` attribute, enabling standard link behaviors, accessibility, and SEO benefits. ```tsx import { Link } from '@tanstack/react-router' function PostCard({ post }: { post: Post }) { return (

{post.title}

{post.excerpt}

) } ``` -------------------------------- ### Bad Example: Using onClick with navigate Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/nav-link-component.md This example shows the incorrect way to handle navigation using `onClick` and `useNavigate()`. It lacks standard link behaviors like right-click to open in a new tab, is not announced as a link to screen readers, and has no valid `href` for SEO. ```tsx import { useNavigate } from '@tanstack/react-router'; // Using onClick with navigate - loses standard link behavior function PostCard({ post }: { post: Post }) { const navigate = useNavigate() return (
navigate({ to: '/posts/$postId', params: { postId: post.id } })} className="post-card" >

{post.title}

{post.excerpt}

) } ``` -------------------------------- ### Split Route Components with Lazy Loading (Good Example) Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/split-lazy-routes.md This example demonstrates code splitting by separating the route configuration and the component into different files. The main route file (`.tsx`) contains critical configuration, while the lazy file (`.lazy.tsx`) holds the component and its on-demand dependencies, reducing the initial bundle size. ```tsx // routes/dashboard.tsx - Only critical config import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/dashboard')({ loader: async ({ context }) => { return context.queryClient.ensureQueryData(dashboardQueries.stats()) }, // No component - it's in the lazy file }) // routes/dashboard.lazy.tsx - Lazy-loaded component import { createLazyFileRoute } from '@tanstack/react-router' import { HeavyChartLibrary } from 'heavy-chart-library' import { ComplexDataGrid } from 'complex-data-grid' import { AnalyticsWidgets } from './components/AnalyticsWidgets' export const Route = createLazyFileRoute('/dashboard')({ component: DashboardPage, pendingComponent: DashboardSkeleton, errorComponent: DashboardError, }) function DashboardPage() { const data = Route.useLoaderData() return (
) } function DashboardSkeleton() { return
Loading dashboard...
} function DashboardError({ error }: { error: Error }) { return
Failed to load dashboard: {error.message}
} ``` -------------------------------- ### Bad Example: No Not-Found Handling Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/err-not-found.md This example shows a router without a default notFoundComponent and a route that throws a generic error instead of a proper 404. This can lead to blank screens or unhelpful error messages. ```tsx // No not-found handling - shows blank screen or error const router = createRouter({ routeTree, // Missing defaultNotFoundComponent }) // Or throwing generic error export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { const post = await fetchPost(params.postId) if (!post) { throw new Error('Not found') // Generic error, not proper 404 } return post }, }) ``` -------------------------------- ### Good Example: Main Route File Needed for Critical Config Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/org-virtual-routes.md This example shows when a main route file (`routes/dashboard.tsx`) is necessary because it contains critical path configurations like `beforeLoad` and `loader`, while the component is still lazy-loaded. ```tsx // routes/dashboard.tsx - Need this for loader/beforeLoad import { createFileRoute, redirect } from '@tanstack/react-router' import { dashboardQueries } from './dashboardQueries' export const Route = createFileRoute('/dashboard')({ beforeLoad: async ({ context }) => { if (!context.auth.isAuthenticated) { throw redirect({ to: '/login' }) } }, loader: async ({ context: { queryClient } }) => { await queryClient.ensureQueryData(dashboardQueries.stats()) }, // Component is in lazy file }) // routes/dashboard.lazy.tsx import { createLazyFileRoute } from '@tanstack/react-router' import { DashboardPage } from './DashboardPage' import { DashboardSkeleton } from './DashboardSkeleton' export const Route = createLazyFileRoute('/dashboard')({ component: DashboardPage, pendingComponent: DashboardSkeleton, }) // Main file IS needed here because we have loader/beforeLoad ``` -------------------------------- ### Good Example: Registering Router Type for Full Type Safety Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/ts-register-router.md This example demonstrates the correct way to register the router instance for type inference. After registration, hooks and components like useNavigate gain full type safety, providing autocompletion and compile-time error checking for routes and parameters. ```tsx // router.tsx import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' export const router = createRouter({ routeTree }) // Register the router instance for type inference declare module '@tanstack/react-router' { interface Register { router: typeof router } } // components/Navigation.tsx import { useNavigate } from '@tanstack/react-router' function Navigation() { const navigate = useNavigate() // Full type safety - TypeScript knows all valid routes navigate({ to: '/posts/$postId', params: { postId: '123' } }) // Type error if route doesn't exist navigate({ to: '/invalid-route' }) // Error: Type '"/invalid-route"' is not assignable... // Autocomplete for params navigate({ to: '/users/$userId/posts/$postId', params: { userId: '1', postId: '2' }, // Both required }) } ``` -------------------------------- ### Good Example: Hybrid Static/Dynamic Routing Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/ssr-prerender.md Combine static prerendering, ISR, and SSR for different routes. This example prerenders the products list, uses ISR for individual product pages with a 5-minute cache, and always SSRs the user-specific cart page with no-store directive. ```tsx // routes/products.tsx - Prerendered export const Route = createFileRoute('/products')({ loader: async () => { // Featured products - prerendered at build const featured = await fetchFeaturedProducts() return { featured } }, }) // routes/products/$productId.tsx - ISR export const Route = createFileRoute('/products/$productId')({ loader: async ({ params }) => { const product = await fetchProduct(params.productId) if (!product) throw notFound() // Cache product pages for 5 minutes setHeaders({ 'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600', }) return { product } }, }) // routes/cart.tsx - Always SSR (user-specific) export const Route = createFileRoute('/cart')({ loader: async ({ context }) => { // No caching - user-specific data setHeaders({ 'Cache-Control': 'private, no-store', }) const cart = await fetchUserCart(context.user.id) return { cart } }, }) ``` -------------------------------- ### Good Example: Next.js App Router SSR Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/ssr-dehydration.md This example demonstrates SSR with TanStack Query using the Next.js App Router. It prefetches queries on the server and hydrates the cache on the client using `HydrationBoundary`. ```tsx // app/posts/page.tsx import { dehydrate, HydrationBoundary, QueryClient, } from '@tanstack/react-query' import { postQueries } from '@/lib/queries' export default async function PostsPage() { const queryClient = new QueryClient() await queryClient.prefetchQuery(postQueries.list()) return ( ) } // components/PostList.tsx 'use client' import { useSuspenseQuery } from '@tanstack/react-query' import { postQueries } from '@/lib/queries' export function PostList() { const { data: posts } = useSuspenseQuery(postQueries.list()) return (
    {posts.map(post => (
  • {post.title}
  • ))}
) } ``` -------------------------------- ### Good Example: Link with Active States Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/nav-link-component.md This example demonstrates how to style active and inactive links using the `` component's `activeProps` and `inactiveProps`. It also shows how to use render props for more granular control over active state styling. ```tsx import { Link } from '@tanstack/react-router' function NavLink({ to, children }: { to: string; children: React.ReactNode }) { return ( {children} ) } // Or use render props for more control function CustomNavLink({ to, children }: { to: string; children: React.ReactNode }) { return ( {({ isActive }) => ( {children} {isActive && } )} ) } ``` -------------------------------- ### Vite Configuration for TanStack Start SSR Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-integration/rules/ssr-dehydrate-hydrate.md Configures Vite for SSR with TanStack Start. The `tanstackStart()` plugin automatically handles SSR entry points, eliminating the need for separate `client.tsx` or `ssr.tsx` files. ```ts // vite.config.ts import { tanstackStart } from "@tanstack/start/plugin/vite" import { defineConfig } from "vite" import react from "@vitejs/plugin-react" export default defineConfig({ plugins: [ tanstackStart(), // Handles SSR entry points automatically react(), ], }) ``` -------------------------------- ### Good Example: createServerFn for POST Mutations Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/sf-create-server-fn.md Shows how to use createServerFn with input validation for creating a post. The handler code runs exclusively on the server. ```tsx // lib/posts.functions.ts import { createServerFn } from '@tanstack/react-start' import { z } from 'zod' import { db } from './db.server' const createPostSchema = z.object({ title: z.string().min(1).max(200), content: z.string().min(1), published: z.boolean().default(false), }) export const createPost = createServerFn({ method: 'POST' }) .inputValidator(createPostSchema) .handler(async ({ data }) => { // This code only runs on the server const post = await db.posts.create({ data: { title: data.title, content: data.content, published: data.published, }, }) return post }) // Usage in component function CreatePostForm() { const createPostMutation = useServerFn(createPost) const handleSubmit = async (formData: FormData) => { try { const post = await createPostMutation({ data: { title: formData.get('title') as string, content: formData.get('content') as string, published: false, }, }) // post is fully typed console.log('Created post:', post.id) } catch (error) { console.error('Failed to create post:', error) } } } ``` -------------------------------- ### When to Use useNavigate Instead Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/nav-link-component.md This section provides examples of scenarios where `useNavigate()` is appropriate, such as after a form submission, after authentication, or for programmatic redirects based on application state. ```tsx import { useNavigate } from '@tanstack/react-router' // 1. After form submission const createPost = useMutation({ mutationFn: submitPost, onSuccess: (data) => { navigate({ to: '/posts/$postId', params: { postId: data.id } }) }, }) // 2. After authentication async function handleLogin(credentials: Credentials) { await login(credentials) navigate({ to: '/dashboard' }) } // 3. Programmatic redirects useEffect(() => { if (!isAuthenticated) { navigate({ to: '/login', search: { redirect: location.pathname } }) } }, [isAuthenticated]) ``` -------------------------------- ### Good Example: Root Route with Context and User Data Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-integration/rules/setup-query-client-context.md Shows how to define root route context including a QueryClient and user data, and how to prefetch global data in `beforeLoad`. ```tsx // 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()({ component: RootComponent, beforeLoad: async ({ context }) => { // Prefetch auth or other global data await context.queryClient.ensureQueryData(authQueryOptions) }, }) function RootComponent() { return ( ) } ``` -------------------------------- ### TanStack Query: No Persistence Example Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/persist-queries.md This example shows a basic TanStack Query setup without any persistence. The cache will be empty on every page refresh, leading to loading states and refetches. ```tsx // No persistence - always starts fresh const queryClient = new QueryClient() function App() { return ( ) } // User refreshes page: // 1. Empty cache // 2. Loading spinners everywhere // 3. Refetch all data // Poor offline experience ``` -------------------------------- ### Good Example: Testing with Mock QueryClient Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-integration/rules/setup-query-client-context.md Demonstrates how to set up a test environment with a mock QueryClient, providing it through router context and wrapping components with QueryClientProvider. ```tsx // 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 }) => ( {children} ), }) return { ...render(), queryClient, } } test('loads posts', async () => { const { queryClient } = renderWithProviders('/posts') // Pre-populate cache for testing queryClient.setQueryData(['posts'], mockPosts) // ... assertions }) ``` -------------------------------- ### Error Boundaries with Streaming SSR Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/ssr-streaming.md This example shows how to implement error boundaries around suspense-enabled components in a streaming SSR setup. Each section can handle its own errors independently. ```tsx function DashboardPage() { return (
{/* Each section handles its own errors */} }> }> }> }>
) } ``` -------------------------------- ### TanStack Start Skill Activation Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/CLAUDE.md Outlines the criteria for activating the tanstack-start skill. This includes identifying server functions, middleware setup, SSR configurations, and authentication flows. ```text - Server functions (`createServerFn`) - Middleware setup - SSR configuration - Authentication flows ``` -------------------------------- ### New TanStack Router Setup Steps Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/CLAUDE.md Outlines the steps for setting up TanStack Router, including loading the skill and applying rules for type safety, file-based routing conventions, and preloading configurations. ```text 1. Load tanstack-router skill 2. Apply `ts-register-router` for type safety 3. Apply `org-file-based-routing` conventions 4. Configure preloading per `preload-intent` ``` -------------------------------- ### Good Example: Clear File Separation Structure Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/file-separation.md Illustrates the recommended directory structure for separating server, client, and shared code using specific file suffixes. ```directory lib/ ├── posts.ts # Shared types and utilities ├── posts.server.ts # Server-only database logic ├── posts.functions.ts # Server function definitions └── schemas/ └── post.ts # Shared validation schemas ``` -------------------------------- ### Good Example: Including Dependencies in Query Key Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/qk-include-dependencies.md Shows the correct way to include all external variables in the query key, ensuring independent caching and automatic refetching when dependencies change. This prevents stale data issues. ```tsx function UserPosts({ userId }: { userId: string }) { // userId included - each user has their own cache entry const { data } = useQuery({ queryKey: ['posts', userId], queryFn: () => fetchPostsByUser(userId), }) return } function FilteredTodos({ status, page }: { status: string; page: number }) { // All dependencies included - refetches when any change const { data } = useQuery({ queryKey: ['todos', { status, page }], queryFn: () => fetchTodos({ status, page }), }) return } ``` -------------------------------- ### AWS Lambda Deployment Configuration Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/deploy-adapters.md Configure the 'aws-lambda' preset for deploying your TanStack Start application to AWS Lambda. This setup is typically used with infrastructure-as-code tools like SST, Serverless Framework, or AWS CDK. ```tsx // app.config.ts import { defineConfig } from '@tanstack/react-start/config' export default defineConfig({ server: { preset: 'aws-lambda', }, }) ``` ```yaml // Deploy with SST, Serverless Framework, or AWS CDK // serverless.yml example: service: my-tanstack-app provider: name: aws runtime: nodejs20.x functions: app: handler: .output/server/index.handler events: - http: ANY / - http: ANY /{proxy+} ``` -------------------------------- ### Bad Example: Missing or Partial Query Invalidation Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/mut-invalidate-queries.md Demonstrates incorrect mutation setups where related queries are not invalidated, leading to stale data. This includes missing `onSuccess` handlers or only invalidating a subset of relevant queries. ```tsx // No invalidation - cache remains stale const createTodo = useMutation({ mutationFn: (newTodo) => api.createTodo(newTodo), // Missing onSuccess handler - todo list won't show new item }) // Partial invalidation - misses related queries const deleteTodo = useMutation({ mutationFn: (todoId) => api.deleteTodo(todoId), onSuccess: () => { // Only invalidates list, not summary/counts queryClient.invalidateQueries({ queryKey: ['todos', 'list'] }) // Missing: ['todos', 'count'], ['todos', 'completed-count'], etc. }, }) ``` -------------------------------- ### Good Example: Auth-Protected Routes with Context Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/ctx-root-context.md Illustrates how to use context for authentication checks in a layout route (`beforeLoad`) and how to access both query client and auth state in nested route loaders. ```tsx // routes/__root.tsx interface RouterContext { queryClient: QueryClient auth: AuthState } export const Route = createRootRouteWithContext()({ component: RootComponent, }) // routes/_authenticated.tsx - Layout route for protected pages export const Route = createFileRoute('/_authenticated')({ beforeLoad: async ({ context, location }) => { if (!context.auth.isAuthenticated) { throw redirect({ to: '/login', search: { redirect: location.href }, }) } }, component: AuthenticatedLayout, }) // routes/_authenticated/dashboard.tsx export const Route = createFileRoute('/_authenticated/dashboard')({ loader: async ({ context: { queryClient, auth } }) => { // We know user is authenticated from parent beforeLoad return queryClient.ensureQueryData( dashboardQueries.forUser(auth.user!.id) ) }, }) ``` -------------------------------- ### Good Example: Server and Public Configuration Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/file-separation.md Demonstrates how to separate sensitive server-only configuration (like API secrets) from public configuration that can be safely exposed to the client. ```tsx // lib/config.server.ts - Server secrets export const config = { databaseUrl: process.env.DATABASE_URL!, sessionSecret: process.env.SESSION_SECRET!, stripeSecretKey: process.env.STRIPE_SECRET_KEY!, } // lib/config.ts - Public config (safe for client) export const publicConfig = { appName: 'My App', apiUrl: process.env.NEXT_PUBLIC_API_URL, stripePublicKey: process.env.NEXT_PUBLIC_STRIPE_KEY, } // Never import config.server.ts on client ``` -------------------------------- ### Default Adapter Configuration (Bad Example) Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/deploy-adapters.md Avoid using default configurations or the wrong adapter for your deployment platform, as this can lead to unexpected behavior or deployment failures. ```tsx // Not configuring adapter - using defaults may not match your host // app.config.ts export default defineConfig({ // No adapter specified // May not work correctly on your deployment platform }) // Or using wrong adapter for platform export default defineConfig({ server: { preset: 'node-server', // But deploying to Vercel Edge }, }) ``` -------------------------------- ### Blocking SSR Example Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/ssr-streaming.md This example shows a blocking SSR approach where the loader waits for all data to complete before sending any HTML. This results in a higher TTFB. ```tsx // Blocking SSR - waits for everything export const Route = createFileRoute('/dashboard')({ loader: async ({ context: { queryClient } }) => { // All of these must complete before ANY HTML is sent await Promise.all([ queryClient.ensureQueryData(userQueries.profile()), // 200ms queryClient.ensureQueryData(dashboardQueries.stats()), // 500ms queryClient.ensureQueryData(activityQueries.recent()), // 300ms queryClient.ensureQueryData(notificationQueries.all()), // 400ms ]) // TTFB: 500ms (slowest query) }, }) ``` -------------------------------- ### Adding Server Functions Steps Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/CLAUDE.md Details the process for adding server functions using TanStack Start, emphasizing the loading of the skill and the application of rules for creating server functions, input validation, and request middleware. ```text 1. Load tanstack-start skill 2. Apply `sf-create-server-fn` patterns 3. Always apply `sf-input-validation` 4. Consider `mw-request-middleware` for auth ``` -------------------------------- ### Multiple useQuery Calls (Bad Example) Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/parallel-use-queries.md Do not call hooks inside loops or conditional statements, as this violates the Rules of Hooks. This example illustrates an incorrect pattern. ```tsx function UserProfiles({ userIds }: { userIds: string[] }) { // Can't call hooks in a loop! const queries = userIds.map(id => useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id), })) } ``` -------------------------------- ### Full-Stack Data Flow Steps Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/CLAUDE.md Guides users through setting up full-stack data flow with TanStack, focusing on integrating Query and Router, setting up context, and applying rules for loader patterns and cache management. ```text 1. Load tanstack-integration skill 2. Apply `setup-query-client-context` 3. Follow `flow-loader-query-pattern` 4. Configure `cache-single-source` ``` -------------------------------- ### Install TanStack Agent Skills Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/README.md Use this command to add the TanStack Agent Skills to your project. This command installs the skills for use with AI coding agents. ```bash npx skills add DeckardGer/tanstack-agent-skills ``` -------------------------------- ### Good Example: Role-Based Access Control Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/auth-route-protection.md Enforce role-based access by checking user roles within `beforeLoad`. This example demonstrates how to restrict access to admin-only routes. ```tsx export const Route = createFileRoute('/_admin')({ beforeLoad: async ({ context }) => { // context.user comes from parent _authenticated route if (context.user.role !== 'admin') { throw redirect({ to: '/unauthorized' }) } }, component: AdminLayout, }) ``` -------------------------------- ### Bad Example: Importing Globals Directly Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/ctx-root-context.md This example shows a common anti-pattern where query clients are imported directly as global variables, leading to tight coupling and difficulties in testing. ```tsx // No context - importing globals directly // routes/__root.tsx import { createRootRoute } from '@tanstack/react-router' import { queryClient } from '@/lib/query-client' // Global import export const Route = createRootRoute({ component: RootComponent, }) // routes/posts.tsx import { queryClient } from '@/lib/query-client' // Import again export const Route = createFileRoute('/posts')({ loader: async () => { // Using global - harder to test, couples to implementation return queryClient.ensureQueryData(postQueries.list()) }, }) ``` -------------------------------- ### Sequential Fetching with useEffect (Bad Example) Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-query/rules/parallel-use-queries.md Avoid sequential fetching within useEffect for multiple data points, as it leads to waterfall requests. This example demonstrates an inefficient approach. ```tsx function UserProfiles({ userIds }: { userIds: string[] }) { const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { async function fetchAll() { const results = [] for (const id of userIds) { const user = await fetchUser(id) // Sequential! results.push(user) } setUsers(results) setLoading(false) } fetchAll() }, [userIds]) // N requests run one after another } ``` -------------------------------- ### Good Example: Parallel Data Fetching in Single Loader Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-router/rules/load-parallel.md Shows how to use `Promise.all` within a single loader or `beforeLoad` to fetch multiple data dependencies concurrently, significantly reducing load times. ```tsx export const Route = createFileRoute('/dashboard')({ beforeLoad: async () => { // All requests start simultaneously const [user, config] = await Promise.all([ fetchUser(), // 200ms fetchAppConfig(), // 150ms ]) // Total: 200ms (parallel) return { user, config } }, loader: async ({ context }) => { // These also run in parallel with each other const [stats, activity, notifications] = await Promise.all([ fetchDashboardStats(context.user.id), fetchRecentActivity(context.user.id), fetchNotifications(context.user.id), ]) return { stats, activity, notifications } }, }) ``` -------------------------------- ### No Input Validation (Bad Example) Source: https://github.com/deckardger/tanstack-agent-skills/blob/main/skills/tanstack-start/rules/sf-input-validation.md This example shows a server function handler that directly trusts client input without any validation, leading to potential security risks and type errors. ```tsx // No validation - trusting client input directly export const updateUser = createServerFn({ method: 'POST' }) .handler(async ({ data }) => { // data is unknown/any - no type safety // SQL injection, invalid data, type errors all possible await db.users.update({ where: { id: data.id }, data: { name: data.name, email: data.email, role: data.role, // Could be set to 'admin' by malicious client! }, }) }) // Weak validation - type assertion without runtime check export const deletePost = createServerFn({ method: 'POST' }) .handler(async ({ data }: { data: { id: string } }) => { // Type assertion doesn't validate at runtime await db.posts.delete({ where: { id: data.id } }) }) ```