TanStack Router
Install this skill
npx skills add jezweb/claude-skillsWorks across Claude Code, Cursor, Codex, Copilot & Antigravity
TanStack Router provides a fully type-safe routing architecture for React applications, emphasizing automatic path generation and strict validation. By adopting a file-based convention, the library maps your filesystem to a route tree, ensuring that links, parameters, and search query strings remain synchronized with your code throughout development. It replaces manual string-based path management with generated types, effectively eliminating broken links and runtime navigation errors. The framework includes built-in support for asynchronous data loading, search parameter validation via Zod, and granular error handling per route. By integrating directly with TanStack Query and leveraging automatic code generation via Vite plugins, it minimizes boilerplate and provides immediate feedback during development, ensuring that navigation logic and state management are intrinsically linked to the underlying route structure.
When to Use This Skill
- β’Building large-scale enterprise SPAs requiring complex nesting
- β’Applications needing strict URL state validation for search filters
- β’Projects that require data pre-fetching before route navigation finishes
- β’Standardizing link and parameter references across large teams
How to Invoke This Skill
Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:
- βSetup type-safe routing for my React project
- βHow to handle search param validation with Zod in React
- βGenerate route types from file structure
- βPrefetch data for a route using TanStack Router
- βImplement an error boundary for a specific route
Pro Tips
- π‘Always define your `createRootRoute()` with an `<Outlet />` in `__root.tsx` to ensure proper rendering of child routes.
- π‘Leverage the Zod adapter for robust URL parameter and search parameter validation, enhancing type safety and preventing runtime errors.
- π‘Organize your route files logically within the `src/routes/` directory to fully utilize the file-based routing auto-generation and maintain clarity.
What this skill does
- β’Automatic file-based route generation and type-safe path mapping
- β’Declarative data fetching with route-level loaders
- β’Native search parameter schema validation using Zod
- β’Deep integration with TanStack Query for cache synchronization
- β’Configurable error boundaries and per-route loading states
When not to use it
- βSmall static sites where standard React Router or basic anchor tags suffice
- βProjects restricted to minimal dependency counts or limited build tool configurations
Example workflow
- Install @tanstack/react-router and the router-plugin
- Configure the Vite plugin to enable auto-generation
- Create a file structure within src/routes for the application map
- Define loaders and search schemas inside individual route files
- Initialize the router using the generated route tree
- Wrap the application in the RouterProvider component
Prerequisites
- βVite
- βTypeScript
- βReact
- βZod (optional for schema validation)
Pitfalls & limitations
- !Vite plugin order is critical; TanStack must precede standard React plugins
- !Manual route tree modifications can conflict with auto-generated code
- !Higher learning curve compared to traditional hook-based routing
FAQ
How it compares
Unlike standard React Router, which relies on runtime string matching, TanStack Router moves the heavy lifting to build-time code generation, turning route navigation into a strictly typed developer experience.
π Full skill instructions β original source: jezweb/claude-skills
Type-safe, file-based routing for React SPAs with route-level data loading and TanStack Query integration
---
## Quick Start
**Last Updated**: 2026-01-09
**Version**: @tanstack/[email protected]
npm install @tanstack/react-router @tanstack/router-devtools
npm install -D @tanstack/router-plugin
# Optional: Zod validation adapter
npm install @tanstack/zod-adapter zod**Vite Config** (TanStackRouterVite MUST come before react()):
// vite.config.ts
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [TanStackRouterVite(), react()], // Order matters!
})**File Structure**:
src/routes/
βββ __root.tsx β createRootRoute() with <Outlet />
βββ index.tsx β createFileRoute('/')
βββ posts.$postId.tsx β createFileRoute('/posts/$postId')**App Setup**:
import { createRouter, RouterProvider } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen' // Auto-generated by plugin
const router = createRouter({ routeTree })
<RouterProvider router={router} />---
## Core Patterns
**Type-Safe Navigation** (routes auto-complete, params typed):
<Link to="/posts/$postId" params={{ postId: '123' }} />
<Link to="/invalid" /> // β TypeScript error**Route Loaders** (data fetching before render):
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => ({ post: await fetchPost(params.postId) }),
component: ({ useLoaderData }) => {
const { post } = useLoaderData() // Fully typed!
return <h1>{post.title}</h1>
},
})**TanStack Query Integration** (prefetch + cache):
const postOpts = (id: string) => queryOptions({
queryKey: ['posts', id],
queryFn: () => fetchPost(id),
})
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postOpts(params.postId)),
component: () => {
const { postId } = Route.useParams()
const { data } = useQuery(postOpts(postId))
return <h1>{data.title}</h1>
},
})---
## Virtual File Routes (v1.140+)
Programmatic route configuration when file-based conventions don't fit your needs:
**Install**:
npm install @tanstack/virtual-file-routes**Vite Config**:
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
target: 'react',
virtualRouteConfig: './routes.ts', // Point to your routes file
}),
react(),
],
})**routes.ts** (define routes programmatically):
import { rootRoute, route, index, layout, physical } from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
index('home.tsx'),
route('/posts', 'posts/posts.tsx', [
index('posts/posts-home.tsx'),
route('$postId', 'posts/posts-detail.tsx'),
]),
layout('first', 'layout/first-layout.tsx', [
route('/nested', 'nested.tsx'),
]),
physical('/classic', 'file-based-subtree'), // Mix with file-based
])**Use Cases**: Custom route organization, mixing file-based and code-based, complex nested layouts.
---
## Search Params Validation (Zod Adapter)
Type-safe URL search params with runtime validation:
**Basic Pattern** (inline validation):
import { z } from 'zod'
export const Route = createFileRoute('/products')({
validateSearch: (search) => z.object({
page: z.number().catch(1),
filter: z.string().catch(''),
sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
}).parse(search),
})**Recommended Pattern** (Zod adapter with fallbacks):
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'
const searchSchema = z.object({
query: z.string().min(1).max(100),
page: fallback(z.number().int().positive(), 1),
sortBy: z.enum(['name', 'date', 'relevance']).optional(),
})
export const Route = createFileRoute('/search')({
validateSearch: zodValidator(searchSchema),
// Type-safe: Route.useSearch() returns typed params
})**Why
.catch() over .default()**: Use .catch() to silently fix malformed params. Use .default() + errorComponent to show validation errors.---
## Error Boundaries
Handle errors at route level with typed error components:
**Route-Level Error Handling**:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
if (!post) throw new Error('Post not found')
return { post }
},
errorComponent: ({ error, reset }) => (
<div>
<p>Error: {error.message}</p>
<button onClick={reset}>Retry</button>
</div>
),
})**Default Error Component** (global fallback):
const router = createRouter({
routeTree,
defaultErrorComponent: ({ error }) => (
<div className="error-page">
<h1>Something went wrong</h1>
<p>{error.message}</p>
</div>
),
})**Not Found Handling**:
export const Route = createFileRoute('/posts/$postId')({
notFoundComponent: () => <div>Post not found</div>,
})---
## Authentication with beforeLoad
Protect routes before they load (no flash of protected content):
**Single Route Protection**:
import { redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard')({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: { redirect: location.pathname }, // Save for post-login
})
}
},
})**Protect Multiple Routes** (layout route pattern):
// routes/(authenticated)/route.tsx - protects all children
export const Route = createFileRoute('/(authenticated)')({
beforeLoad: async ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
},
})**Passing Auth Context** (from React hooks):
// main.tsx - pass auth state to router
function App() {
const auth = useAuth() // Your auth hook
return (
<RouterProvider
router={router}
context={{ auth }} // Available in beforeLoad
/>
)
}---
## Known Issues Prevention
This skill prevents **20** documented issues:
### Issue #1: Devtools Dependency Resolution
- **Error**: Build fails with
@tanstack/router-devtools-core not found- **Fix**:
npm install @tanstack/router-devtools**Issue #2: Vite Plugin Order** (CRITICAL)
- **Error**: Routes not auto-generated,
routeTree.gen.ts missing- **Fix**: TanStackRouterVite MUST come before react() in plugins array
- **Why**: Plugin processes route files before React compilation
**Issue #3: Type Registration Missing**
- **Error**:
<Link to="..."> not typed, no autocomplete- **Fix**: Import
routeTree from ./routeTree.gen in main.tsx to register types**Issue #4: Loader Not Running**
- **Error**: Loader function not called on navigation
- **Fix**: Ensure route exports
Route constant: export const Route = createFileRoute('/path')({ loader: ... })**Issue #5: Memory Leak with TanStack Form** (FIXED)
- **Error**: Production crashes when using TanStack Form + Router
- **Source**: GitHub Issue #5734 (closed Jan 5, 2026)
- **Resolution**: Fixed in latest versions of @tanstack/form and @tanstack/react-start. Update both packages to resolve.
**Issue #6: Virtual Routes Index/Layout Conflict**
- **Error**: route.tsx and index.tsx conflict when using
physical() in virtual routing- **Source**: GitHub Issue #5421
- **Fix**: Use pathless route instead:
_layout.tsx + _layout.index.tsx**Issue #7: Search Params Type Inference**
- **Error**: Type inference not working with
zodSearchValidator- **Source**: GitHub Issue #3100 (regression since v1.81.5)
- **Fix**: Use
zodValidator from @tanstack/zod-adapter instead**Issue #8: TanStack Start Validators on Reload**
- **Error**:
validateSearch not working on page reload in TanStack Start- **Source**: GitHub Issue #3711
- **Note**: Works on client-side navigation, fails on direct page load
### Issue #9: Server Function Validation Errors Lose Structure
**Error**:
inputValidator Zod errors stringified, losing structure on client**Source**: [GitHub Issue #6428](https://github.com/TanStack/router/issues/6428)
**Why It Happens**: TanStack Start server function error serialization converts Zod issues array to JSON string in
error.message, making it unusable without manual parsing.**Prevention**:
// Server function with input validation
export const myFn = createServerFn({ method: 'POST' })
.inputValidator(z.object({
name: z.string().min(2),
age: z.number().min(18),
}))
.handler(async ({ data }) => data)
// Client: Workaround to parse stringified issues
try {
await mutation.mutate({ data: invalidData })
} catch (error) {
if (error.message.startsWith('[')) {
const issues = JSON.parse(error.message)
// Now can use structured error data
issues.forEach(issue => {
console.log(issue.path, issue.message)
})
}
}**Official Status**: Known issue, tracking PR for fix
### Issue #10: useParams({ strict: false }) Returns Unparsed Values
**Error**: Params typed as parsed but returned as strings after navigation
**Source**: [GitHub Issue #6385](https://github.com/TanStack/router/issues/6385)
**Why It Happens**: In v1.147.3+,
match.params is no longer parsed when using strict: false. First render works correctly, but after navigation values are stored as strings instead of parsed types.**Prevention**:
// Route with param parsing
export const Route = createFileRoute('/posts/$postId')({
params: {
parse: (params) => ({
postId: z.coerce.number().parse(params.postId),
}),
},
})
// Component: Use strict mode (default) for parsed params
function Component() {
const { postId } = useParams() // β Parsed as number
// const { postId } = useParams({ strict: false }) // β String!
// Or manually parse when using strict: false
const params = useParams({ strict: false })
const postId = Number(params.postId)
}**Official Status**: Known issue, workaround required
### Issue #11: Pathless Route notFoundComponent Not Rendering
**Error**:
notFoundComponent on pathless layout routes ignored**Source**: [GitHub Issue #6351](https://github.com/TanStack/router/issues/6351), [GitHub Issue #4065](https://github.com/TanStack/router/issues/4065)
**Why It Happens**: Pathless routes (e.g.,
routes/(authenticated)/route.tsx) don't render their notFoundComponent. Instead, the defaultNotFoundComponent from router config is triggered. This has been broken since April 2025.**Prevention**:
// β Doesn't work: notFoundComponent on pathless layout
export const Route = createFileRoute('/(authenticated)')({
beforeLoad: ({ context }) => {
if (!context.auth) throw redirect({ to: '/login' })
},
notFoundComponent: () => <div>Protected 404</div>, // Not rendered!
})
// β Works: Define on child routes instead
export const Route = createFileRoute('/(authenticated)/dashboard')({
notFoundComponent: () => <div>Protected 404</div>,
})**Official Status**: Known issue, workaround required
### Issue #12: Aborted Loader Renders errorComponent with Undefined Error
**Error**: Rapid navigation aborts previous loader and renders errorComponent with
undefined error**Source**: [GitHub Issue #6388](https://github.com/TanStack/router/issues/6388)
**Why It Happens**: Side effect introduced after PR #4570. When user rapidly navigates (e.g., clicking through list items), aborted fetch requests trigger errorComponent without passing the abort error.
**Prevention**:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params, abortController }) => {
await fetch(/api/posts/${params.postId}, {
signal: abortController.signal,
})
},
errorComponent: ({ error, reset }) => {
// Check for undefined error (aborted request)
if (!error) {
return null // Or show loading state
}
return <div>Error: {error.message}</div>
},
})**Official Status**: Known issue, workaround required
### Issue #13: Vitest Cannot Read Properties of Null (useState)
**Error**:
Cannot read properties of null (reading 'useState') when running tests with Vitest**Source**: [GitHub Issue #6262](https://github.com/TanStack/router/issues/6262), [PR #6074](https://github.com/TanStack/router/pull/6074)
**Why It Happens**: TanStack Start's
tanstackStart() plugin conflicts with Vitest's React hooks rendering. This is a known duplicate issue with a PR in progress.**Prevention**:
// Temporary workaround: Comment out tanstackStart() for tests
// vite.config.ts
export default defineConfig({
plugins: [
// tanstackStart(), // Disable for tests
react(),
],
test: { environment: 'jsdom' },
})**Official Status**: PR #6074 in progress to fix
### Issue #14: Throwing Error in Streaming SSR Loader Crashes Dev Server
**Error**: Dev server crashes when route loader throws error without awaiting (using
void instead of await)**Source**: [GitHub Issue #6200](https://github.com/TanStack/router/issues/6200)
**Why It Happens**: SSR streaming mode can't handle unawaited promise rejections. The error escapes the loader context and crashes the worker process.
**Prevention**:
// β Wrong: void + throw crashes dev server
export const Route = createFileRoute('/posts')({
loader: async () => {
void fetch('/api/posts').then(r => {
throw new Error('boom') // Crashes!
})
},
})
// β Correct: Always await or catch
export const Route = createFileRoute('/posts')({
loader: async () => {
try {
const data = await fetch('/api/posts')
return data
} catch (error) {
throw error // Caught by errorComponent
}
},
})**Official Status**: Known issue, workaround required
### Issue #15: Prerender Hangs Indefinitely if Filter Returns Zero Results
**Error**: Build step hangs when
prerender.filter returns zero routes**Source**: [GitHub Issue #6425](https://github.com/TanStack/router/issues/6425)
**Why It Happens**: TanStack Start prerendering doesn't handle empty route sets gracefully - it waits indefinitely for routes that never come.
**Prevention**:
// β Wrong: Empty filter causes hang
tanstackStart({
prerender: {
enabled: true,
filter: (route) => false, // No routes β hangs!
},
})
// β Correct: Ensure at least one route or disable
tanstackStart({
prerender: {
enabled: true,
filter: (route) => route.path === '/' || route.path.startsWith('/posts'),
},
})
// Or temporarily disable
tanstackStart({
prerender: { enabled: false },
})**Official Status**: Known issue, workaround required
### Issue #16: Prerendering Does Not Work in Docker
**Error**: Build fails in Docker with "Unable to connect" during prerender step
**Source**: [GitHub Issue #6275](https://github.com/TanStack/router/issues/6275), [PR #6305](https://github.com/TanStack/router/pull/6305)
**Why It Happens**: Vite preview server used for prerendering is not accessible in Docker environment.
**Prevention**:
// vite.config.ts - Make preview server accessible in Docker
export default defineConfig({
preview: {
host: true, // Bind to 0.0.0.0 instead of localhost
},
plugins: [
devtools(),
// nitro({ preset: "bun" }), // Remove temporarily if issues persist
tanstackStart(),
react(),
],
})**Official Status**: PR #6305 in progress
### Issue #17: Route Head Function Executes Before Loader Finishes
**Error**: Meta tags generated with incomplete data when
head() runs before loader()**Source**: [GitHub Issue #6221](https://github.com/TanStack/router/issues/6221)
**Why It Happens**: The
head() function can execute before the route loader() finishes, causing meta tags to use placeholder or undefined data.**Prevention**:
// β Wrong: loaderData may not be available yet
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
head: ({ loaderData }) => ({
meta: [
{ title: loaderData.post.title }, // May be undefined!
],
}),
})
// β Correct: Explicitly await if needed
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post }
},
head: async ({ loaderData }) => {
await loaderData // Ensure loaded
return {
meta: [{ title: loaderData.post.title }],
}
},
})**Official Status**: Known issue, workaround required
### Issue #18: Virtual Routes Don't Support Manual Lazy Loading (Community-sourced)
**Error**:
createLazyFileRoute automatically replaced with createFileRoute in virtual routes**Source**: [GitHub Issue #6396](https://github.com/TanStack/router/issues/6396)
**Why It Happens**: Virtual file routes are designed for automatic code splitting only. Manual lazy routes are not supported - the plugin silently replaces them.
**Prevention**:
// Virtual routes: Use automatic code splitting
// vite.config.ts
tanstackRouter({
target: 'react',
virtualRouteConfig: './routes.ts',
autoCodeSplitting: true, // Use automatic splitting
})
// Don't use createLazyFileRoute in virtual routes
// It will be replaced with createFileRoute automatically**Official Status**: By design (documented behavior)
### Issue #19: NavigateOptions Type Safety Inconsistency (Community-sourced)
**Error**:
NavigateOptions type doesn't enforce required params like useNavigate() does**Source**: [TkDodo's Blog: The Beauty of TanStack Router](https://tkdodo.eu/blog/the-beauty-of-tan-stack-router)
**Why It Happens**: Type definitions differ between runtime hook and type helper.
NavigateOptions is less strict.**Prevention**:
// β Wrong: NavigateOptions doesn't catch missing params
const options: NavigateOptions = {
to: '/posts/$postId', // No TS error, but params required!
}
// β Correct: Use useNavigate() return type
const navigate = useNavigate()
type NavigateFn = typeof navigate
// Now type-safe across all usages**Verified**: Cross-referenced with TanStack Query maintainer analysis
### Issue #20: Missing Leading Slash in Route Paths (Community-sourced)
**Error**: Routes fail to match when path defined without leading slash
**Source**: [Official Debugging Guide](https://tanstack.com/router/latest/docs/framework/react/how-to/debug-router-issues)
**Why It Happens**: Very common beginner mistake - using
'about' instead of '/about' causes route matching failures.**Prevention**:
// β Wrong: Missing leading slash
export const Route = createFileRoute('about')({ /* ... */ })
// β Correct: Always start with /
export const Route = createFileRoute('/about')({ /* ... */ })**Verified**: Official documentation, common debugging issue
---
## Cloudflare Workers Integration
**Vite Config** (add @cloudflare/vite-plugin):
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [TanStackRouterVite(), react(), cloudflare()],
})**API Routes Pattern** (fetch from Workers backend):
// Worker: functions/api/posts.ts
export async function onRequestGet({ env }) {
const { results } = await env.DB.prepare('SELECT * FROM posts').all()
return Response.json(results)
}
// Router: src/routes/posts.tsx
export const Route = createFileRoute('/posts')({
loader: async () => fetch('/api/posts').then(r => r.json()),
})---
**Related Skills**: tanstack-query (data fetching), react-hook-form-zod (form validation), cloudflare-worker-base (API backend), tailwind-v4-shadcn (UI)
**Related Packages**: @tanstack/zod-adapter (search validation), @tanstack/virtual-file-routes (programmatic routes)
---
**Last verified**: 2026-01-20 | **Skill version**: 2.0.0 | **Changes**: Added 12 new issues from community research (inputValidator structure loss, useParams parsing bug, pathless notFoundComponent, aborted loader errors, Vitest conflicts, SSR streaming crashes, Docker prerender issues, head/loader timing, virtual routes lazy loading limitation, NavigateOptions type inconsistency, leading slash common mistake). Increased error prevention from 8 to 20 documented issues.
---
---
paths: "**/*.ts", "**/*.tsx", "**/route*.ts", "**/router*.ts"
---
# TanStack Router v1 Corrections
Claude's training may have limited TanStack Router knowledge. This project uses **TanStack Router v1**.
## File-Based Routing (Recommended)
/* β Manual route definitions (verbose) */
const router = createRouter({
routeTree: rootRoute.addChildren([
indexRoute,
aboutRoute,
]),
})
/* β
File-based routing with routeTree.gen.ts */
// routes/__root.tsx, routes/index.tsx, routes/about.tsx
// Then import generated tree:
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })## Route File Structure
/* β Wrong file naming */
// routes/About.tsx - capitalized won't work
// routes/about/page.tsx - Next.js convention
/* β
TanStack Router conventions */
// routes/__root.tsx - root layout
// routes/index.tsx - home page (/)
// routes/about.tsx - /about
// routes/posts/$postId.tsx - /posts/:postId (dynamic)
// routes/posts/index.tsx - /posts## createFileRoute vs createRoute
/* β Using createRoute in file-based routing */
import { createRoute } from '@tanstack/react-router'
const Route = createRoute({...})
/* β
Use createFileRoute for file-based */
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutPage,
})## Loader Pattern
/* β React Router style loader */
export async function loader({ params }) {
return fetch(/api/posts/${params.id})
}
/* β
TanStack Router loader */
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return fetchPost(params.postId)
},
component: PostPage,
})
function PostPage() {
const post = Route.useLoaderData()
return <div>{post.title}</div>
}## Search Params
/* β Manual URLSearchParams */
const params = new URLSearchParams(window.location.search)
/* β
Type-safe search params */
export const Route = createFileRoute('/search')({
validateSearch: (search) => ({
q: search.q as string || '',
page: Number(search.page) || 1,
}),
})
function SearchPage() {
const { q, page } = Route.useSearch()
}## Navigation
/* β useNavigate like React Router */
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
navigate('/about')
/* β
TanStack Router navigation */
import { useNavigate, Link } from '@tanstack/react-router'
const navigate = useNavigate()
navigate({ to: '/about' })
// Or type-safe Link
<Link to="/posts/$postId" params={{ postId: '123' }}>
View Post
</Link>## Quick Fixes
| If Claude suggests... | Use instead... |
|----------------------|----------------|
|
createRoute in file-based | createFileRoute ||
routes/About.tsx | routes/about.tsx (lowercase) ||
loader({ params }) export | loader inside createFileRoute ||
useParams() | Route.useParams() ||
useSearchParams() | Route.useSearch() with validateSearch ||
navigate('/path') | navigate({ to: '/path' }) |How to Use This Skill Unit
Option A: Project-Specific (Recommended)
- Click "Download" above
- In your project, create the directory:
.agent/skills/tanstack-router/ - Save the file as
SKILL.md - The agent will automatically discover the skill based on its description.
Option B: Global Installation (All Agents)
Save the file to these locations to make it available across all projects:
- Claude Code:
~/.claude/skills/jezweb/claude-skills/tanstack-router/SKILL.md - Cursor:
~/.cursor/skills/jezweb/claude-skills/tanstack-router/SKILL.md - Antigravity:
~/.gemini/antigravity/skills/jezweb/claude-skills/tanstack-router/SKILL.md
π Install with CLI:npx skills add jezweb/claude-skills
