Smithery Logo
MCPsSkillsDocsPricing
Login
NewFlame, an assistant that learns and improves. Available onTelegramSlack
    retrip-ai

    tanstack-comprehensive

    retrip-ai/tanstack-comprehensive
    Coding
    2

    About

    SKILL.md

    Install

    • Telegram
      Telegram
    • Slack
      Slack
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    • Download skill
    ├─
    ├─
    └─
    Smithery Logo

    Give agents more agency

    Resources

    DocumentationPrivacy PolicySystem Status

    Company

    PricingAboutBlog

    Connect

    © 2026 Smithery. All rights reserved.

    About

    Tanstack Start, Router, and Query patterns for routing, data fetching, server functions, and tRPC integration...

    SKILL.md

    Tanstack Comprehensive

    Complete guide to Tanstack Start, Router, and Query patterns for full-stack type-safe applications.

    Overview

    This skill covers the complete Tanstack ecosystem used in this project:

    • Tanstack Router - File-based routing with type-safe navigation
    • Tanstack Query - Data fetching, caching, and state management
    • Tanstack Start - Server-side rendering and data loading
    • tRPC Integration - Type-safe API calls

    When to Apply

    Reference these guidelines when:

    • Creating or modifying routes
    • Implementing data fetching (client or server-side)
    • Working with loaders, queries, or mutations
    • Handling navigation or URL parameters
    • Integrating forms with server operations
    • Optimizing data loading patterns

    Quick Reference

    Critical Patterns

    File-Based Routing:

    • Pages: dashboard/index.tsx → /dashboard
    • Layouts: _authenticated.tsx → Wraps child routes
    • Dynamic: users/$id.tsx → /users/123

    Data Loading (Recommended Pattern):

    // loader + pendingComponent + useSuspenseQuery
    export const Route = createFileRoute('/page')({
      loader: async ({ context }) => {
        await context.queryClient.ensureQueryData(
          context.trpc.myData.queryOptions()
        );
      },
      pendingComponent: MySkeleton,
      component: MyPage,
    });
    
    function MyPage() {
      const trpc = useTrpc();
      const { data } = useSuspenseQuery(trpc.myData.queryOptions());
      return <div>{data.name}</div>;
    }
    

    tRPC Integration:

    // ✅ CORRECT
    const { data } = useQuery(trpc.organizations.getCurrent.queryOptions());
    
    // ❌ WRONG - This method doesn't exist
    const { data } = trpc.organizations.getCurrent.useQuery();
    

    Navigation:

    <Link to="/users/$id" params={{ id: '123' }}>User</Link>
    

    Search Params (Avoid Re-renders):

    // ✅ Read on demand
    const router = useRouter();
    const ref = router.latestLocation.search.ref;
    
    // ❌ Subscribes to all changes
    const search = useSearch({ from: '__root__' });
    const ref = search.ref;
    

    Architecture

    workspace (Tanstack Start)
      ↓ tRPC client
    api-trpc (Hono + tRPC)
      ↓ Drizzle ORM
    Database (PostgreSQL)
    

    Key Principle: Never access database directly. Always use tRPC procedures.

    References

    Complete documentation with examples:

    • references/routing.md - File-based routing, navigation, params, loaders, authentication
    • references/data-fetching.md - Queries, mutations, tRPC integration, SSR patterns, Mastra queries
    • references/server-functions.md - Server-side operations, tRPC from workspace, error handling

    To find specific patterns:

    grep -l "loader" references/*.md
    grep -l "useSuspenseQuery" references/*.md
    grep -l "search params" references/*.md
    

    Core Concepts

    1. Routing Patterns

    File Structure Determines URLs:

    • index.tsx → Route root (e.g., /dashboard)
    • _layout.tsx → Layout wrapper (doesn't affect URL)
    • $param.tsx → Dynamic segment
    • Nested folders → Nested routes

    Type-Safe Navigation:

    // Link component
    <Link to="/users/$id" params={{ id }}>User</Link>
    
    // Programmatic
    const navigate = useNavigate();
    navigate({ to: '/dashboard', search: { tab: 'overview' } });
    
    // In component
    const { id } = Route.useParams(); // Type-safe
    const { tab } = Route.useSearch(); // Type-safe
    

    2. Data Loading Patterns

    Recommended: loader + pendingComponent + useSuspenseQuery

    Why this pattern?

    • Skeleton shows immediately (no blank screen)
    • No duplicate queries (loader preloads, component reads cache)
    • Simpler component code (data always available)
    • Better UX (immediate visual feedback)
    export const Route = createFileRoute('/dashboard')({
      loader: async ({ context }) => {
        // Preload all data in parallel
        await Promise.all([
          context.queryClient.ensureQueryData(
            context.trpc.organizations.getCurrent.queryOptions()
          ),
          context.queryClient.ensureQueryData(
            context.trpc.members.list.queryOptions()
          ),
        ]);
      },
      pendingComponent: DashboardSkeleton,
      component: DashboardPage,
    });
    
    function DashboardPage() {
      const trpc = useTrpc();
      // Data guaranteed - no loading checks needed
      const { data: org } = useSuspenseQuery(
        trpc.organizations.getCurrent.queryOptions()
      );
      const { data: members } = useSuspenseQuery(
        trpc.members.list.queryOptions()
      );
    
      return <div>{org.name} - {members.length} members</div>;
    }
    

    Anti-Pattern: useQuery + manual loading check

    // ❌ DON'T DO THIS
    function MyPage() {
      const { data, isLoading } = useQuery(...);
    
      if (isLoading || !data) {
        return <MySkeleton />; // Causes blank screen flash
      }
    
      return <div>{data.name}</div>;
    }
    

    3. tRPC Integration

    CRITICAL: The project uses createTRPCOptionsProxy which provides factory functions, NOT hooks.

    Correct Usage:

    import { useQuery, useMutation } from '@tanstack/react-query';
    import { trpc } from '@/trpc';
    
    // Queries
    const { data } = useQuery(
      trpc.organizations.getCurrent.queryOptions()
    );
    
    // With params
    const { data } = useQuery(
      trpc.users.getById.queryOptions({ id: '123' })
    );
    
    // Mutations
    const mutation = useMutation(
      trpc.organizations.update.mutationOptions()
    );
    
    mutation.mutate({ name: 'New Name' });
    

    Query Invalidation:

    import { useQueryClient } from '@tanstack/react-query';
    
    const queryClient = useQueryClient();
    
    // Invalidate specific query
    queryClient.invalidateQueries({
      queryKey: [['organizations', 'getCurrent']],
    });
    
    // Invalidate all organization queries
    queryClient.invalidateQueries({
      queryKey: [['organizations']],
    });
    

    4. Search Params Optimization

    Problem: useSearch() subscribes to ALL search param changes, causing unnecessary re-renders.

    Solution: Read search params on-demand using router.latestLocation.

    // ✅ CORRECT - No re-renders
    import { useRouter } from '@tanstack/react-router';
    
    function ShareButton({ chatId }: Props) {
      const router = useRouter();
    
      const handleShare = () => {
        const ref = router.latestLocation.search.ref;
        shareChat(chatId, { ref });
      };
    
      return <button onClick={handleShare}>Share</button>;
    }
    
    // ❌ WRONG - Re-renders on every search param change
    import { useSearch } from '@tanstack/react-router';
    
    function ShareButton({ chatId }: Props) {
      const search = useSearch({ from: '__root__' });
    
      const handleShare = () => {
        const ref = search.ref;
        shareChat(chatId, { ref });
      };
    
      return <button onClick={handleShare}>Share</button>;
    }
    

    5. Route Loaders

    Pre-load data before navigation:

    export const Route = createFileRoute('/_authenticated/dashboard')({
      loader: async ({ context }) => {
        // Blocks navigation until data loaded
        await context.queryClient.ensureQueryData(
          context.trpc.organizations.getCurrent.queryOptions()
        );
      },
      pendingComponent: DashboardSkeleton,
      component: DashboardPage,
    });
    

    6. Protected Routes

    import { createFileRoute, redirect } from '@tanstack/react-router';
    
    export const Route = createFileRoute('/_authenticated')({
      beforeLoad: async ({ context }) => {
        const session = await getSession(context);
    
        if (!session) {
          throw redirect({
            to: '/sign-in',
            search: { redirect: location.href },
          });
        }
      },
    });
    

    7. Mastra Queries

    For AI chat data (messages, threads), use query functions instead of tRPC:

    // src/lib/mastra-queries.ts
    export const threadMessagesQueryOptions = (threadId: string) => ({
      queryKey: ['mastra', 'messages', threadId] as const,
      queryFn: async () => {
        const client = createMastraClient();
        const { messages } = await client.listThreadMessages(threadId, {
          agentId: 'retripAgent',
        });
        return toAISdkV5Messages(messages);
      },
    });
    
    // In component
    const { data: messages } = useSuspenseQuery(
      threadMessagesQueryOptions(threadId)
    );
    

    Best Practices

    ✅ Do:

    Routing:

    • Use file-based routing for all pages
    • Leverage type-safe params and search
    • Use Link component for navigation
    • Protect routes with beforeLoad
    • Validate search params with Zod

    Data Fetching:

    • Use ensureQueryData in loaders with pendingComponent
    • Use useSuspenseQuery for loader-prefetched data
    • Use queryOptions() with TanStack Query hooks
    • Invalidate queries after mutations
    • Handle errors gracefully

    Performance:

    • Read search params on-demand (not reactively)
    • Preload routes before navigation
    • Load data in parallel with Promise.all()
    • Use optimistic updates for instant feedback

    ❌ Don't:

    Routing:

    • Mix file-based and programmatic routing
    • Use plain <a> tags for internal navigation
    • Skip search param validation
    • Create deeply nested layouts unnecessarily

    Data Fetching:

    • Use non-existent .useQuery() method on trpc object
    • Use useQuery + manual if (!data) checks for loader data
    • Forget pendingComponent when using loaders
    • Skip error handling
    • Query database directly from workspace
    • Use any types

    Performance:

    • Subscribe to all search params when you only need one
    • Skip route preloading on hover/focus
    • Load data sequentially when it could be parallel

    Common Patterns

    Mutation with Invalidation

    const queryClient = useQueryClient();
    
    const mutation = useMutation({
      ...trpc.organizations.update.mutationOptions(),
      onSuccess: () => {
        queryClient.invalidateQueries({
          queryKey: [['organizations', 'getCurrent']],
        });
        toast.success('Updated successfully');
      },
    });
    

    Optimistic Updates

    const mutation = useMutation({
      ...trpc.apiKeys.delete.mutationOptions(),
      onMutate: async (deletedId) => {
        await queryClient.cancelQueries({
          queryKey: [['apiKeys', 'list']],
        });
    
        const previous = queryClient.getQueryData([['apiKeys', 'list']]);
    
        queryClient.setQueryData([['apiKeys', 'list']], (old: any) =>
          old?.filter((key: any) => key.id !== deletedId)
        );
    
        return { previous };
      },
      onError: (err, deletedId, context) => {
        queryClient.setQueryData([['apiKeys', 'list']], context?.previous);
      },
    });
    

    Dependent Queries

    const { data: user } = useQuery(
      trpc.users.getById.queryOptions({ id: userId })
    );
    
    const { data: posts } = useQuery({
      ...trpc.posts.list.queryOptions({ authorId: user?.id }),
      enabled: !!user,
    });
    

    Route Preloading

    import { Link, useRouter } from '@tanstack/react-router';
    
    function Navigation() {
      const router = useRouter();
    
      return (
        <Link
          to="/dashboard"
          onMouseEnter={() => router.preloadRoute('/dashboard')}
          onFocus={() => router.preloadRoute('/dashboard')}
        >
          Dashboard
        </Link>
      );
    }
    

    Related Skills

    • react-best-practices - Performance optimization patterns
    • form-patterns - Form handling with React Hook Form

    Version: 1.0.0 Last updated: 2026-01-14

    Recommended Servers
    ThinAir Data
    ThinAir Data
    Local Model Suitability MCP
    Local Model Suitability MCP
    Blockscout MCP Server
    Blockscout MCP Server
    Repository
    retrip-ai/agent-skills
    Files