Agent SkillsAgent Skills
captjay98

TanStack Router

@captjay98/TanStack Router
captjay98
0
0 forks
Updated 5/5/2026
View on GitHub

File-based routing, loaders, and navigation patterns in LivestockAI

Installation

$npx agent-skills-cli install @captjay98/TanStack Router
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Path.kiro/skills/tanstack-router/SKILL.md
Branchmain
Scoped Name@captjay98/TanStack Router

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

npx agent-skills-cli list

Skill Instructions


name: TanStack Router description: File-based routing, loaders, and navigation patterns in LivestockAI

TanStack Router

LivestockAI uses TanStack Router for type-safe, file-based routing with SSR support.

Route Structure

Routes are in app/routes/:

app/routes/
β”œβ”€β”€ __root.tsx           # Root layout
β”œβ”€β”€ index.tsx            # Public landing (/)
β”œβ”€β”€ _auth.tsx            # Auth layout wrapper
└── _auth/               # Protected routes
    β”œβ”€β”€ dashboard.tsx    # /dashboard
    β”œβ”€β”€ batches/
    β”‚   β”œβ”€β”€ index.tsx    # /batches
    β”‚   └── $batchId.tsx # /batches/:batchId
    β”œβ”€β”€ farms/
    β”‚   β”œβ”€β”€ index.tsx    # /farms
    β”‚   └── $farmId.tsx  # /farms/:farmId
    └── settings.tsx     # /settings

Route Definition Pattern

Use loaders for data fetching, not useEffect:

import { createFileRoute } from '@tanstack/react-router'
import { getBatchesForFarmFn } from '~/features/batches/server'
import { BatchesSkeleton } from '~/components/batches/batches-skeleton'

export const Route = createFileRoute('/_auth/batches/')({
  // 1. Validate search params
  validateSearch: (search) => ({
    farmId: search.farmId as string | undefined,
    page: Number(search.page) || 1,
    status: search.status as string | undefined,
  }),

  // 2. Define loader dependencies
  loaderDeps: ({ search }) => ({
    farmId: search.farmId,
    page: search.page,
    status: search.status,
  }),

  // 3. Loader - fetches data on server
  loader: async ({ deps }) => {
    return getBatchesForFarmFn({ data: deps })
  },

  // 4. Loading state
  pendingComponent: BatchesSkeleton,

  // 5. Error state
  errorComponent: ({ error }) => (
    <div className="p-4 text-red-600">
      Error: {error.message}
    </div>
  ),

  // 6. Main component
  component: BatchesPage,
})

function BatchesPage() {
  // Access loader data with full type safety
  const { paginatedBatches, summary } = Route.useLoaderData()

  return (
    <div>
      <h1>Batches</h1>
      {/* Render data */}
    </div>
  )
}

Anti-Pattern: useEffect for Data

// ❌ WRONG - Don't do this
function BatchesPage() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    getBatchesForFarmFn({ data: {} }).then(setData)
  }, [])

  if (loading) return <div>Loading...</div>
  return <div>{/* render */}</div>
}

Dynamic Routes

Use $paramName for dynamic segments:

// app/routes/_auth/batches/$batchId.tsx
export const Route = createFileRoute('/_auth/batches/$batchId')({
  loader: async ({ params }) => {
    return getBatchDetailsFn({ data: { batchId: params.batchId } })
  },
  component: BatchDetailPage,
})

function BatchDetailPage() {
  const { batch, stats } = Route.useLoaderData()
  const { batchId } = Route.useParams()
  // ...
}

Navigation

import { Link, useNavigate } from '@tanstack/react-router'

// Declarative navigation
<Link to="/batches/$batchId" params={{ batchId: '123' }}>
  View Batch
</Link>

// Programmatic navigation
const navigate = useNavigate()
navigate({ to: '/batches', search: { status: 'active' } })

Search Params

// Reading search params
const { farmId, status } = Route.useSearch()

// Updating search params
<Link
  to="."
  search={(prev) => ({ ...prev, status: 'active' })}
>
  Active Only
</Link>

Skeleton Components

Create skeleton components for pendingComponent:

// app/components/batches/batches-skeleton.tsx
import { Skeleton } from '~/components/ui/skeleton'

export function BatchesSkeleton() {
  return (
    <div className="space-y-4">
      <div className="grid gap-4 md:grid-cols-4">
        {Array.from({ length: 4 }).map((_, i) => (
          <Skeleton key={i} className="h-32 w-full" />
        ))}
      </div>
      <Skeleton className="h-64 w-full" />
    </div>
  )
}

Auth Layout

The _auth.tsx layout wraps protected routes:

// app/routes/_auth.tsx
export const Route = createFileRoute('/_auth')({
  beforeLoad: async () => {
    const session = await getSession()
    if (!session) {
      throw redirect({ to: '/login' })
    }
  },
  component: AuthLayout,
})

function AuthLayout() {
  return (
    <AppShell>
      <Outlet />
    </AppShell>
  )
}

Related Skills

  • tanstack-start - Server functions
  • tanstack-query - Client-side mutations
  • rugged-utility - UI patterns for loading states