Claude-code-kit tanstack-router
TanStack Router file-based routing patterns including route creation, navigation, loaders, type-safe routing, and lazy loading. Use when creating routes, implementing navigation, or working with TanStack Router.
install
source · Clone the upstream repo
git clone https://github.com/Squirrelfishcityhall150/claude-code-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Squirrelfishcityhall150/claude-code-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/cli/kits/tanstack-router/skills/tanstack-router" ~/.claude/skills/squirrelfishcityhall150-claude-code-kit-tanstack-router && rm -rf "$T"
manifest:
cli/kits/tanstack-router/skills/tanstack-router/SKILL.mdsource content
TanStack Router Patterns
Purpose
File-based routing with TanStack Router, emphasizing type-safe navigation, route loaders, and lazy loading.
When to Use This Skill
- Creating new routes
- Implementing navigation
- Using route loaders for data
- Type-safe routing with parameters
- Lazy loading routes
Quick Start
Basic Route
// routes/posts/index.tsx import { createFileRoute } from '@tanstack/react-router'; import { postsApi } from '~/features/posts/api/postsApi'; export const Route = createFileRoute('/posts')({ loader: async () => { const posts = await postsApi.getAll(); return { posts }; }, component: PostsPage, }); function PostsPage() { const { posts } = Route.useLoaderData(); return ( <div> <h1>Posts</h1> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ); }
File-Based Routing
Directory Structure
routes/ ├── __root.tsx # Root route ├── index.tsx # / ├── about.tsx # /about ├── posts/ │ ├── index.tsx # /posts │ └── $postId.tsx # /posts/:postId └── users/ ├── index.tsx # /users └── $userId/ ├── index.tsx # /users/:userId └── posts.tsx # /users/:userId/posts
Route Mapping
File Path → URL Path routes/index.tsx → / routes/about.tsx → /about routes/posts/index.tsx → /posts routes/posts/$postId.tsx → /posts/:postId routes/users/$userId/index.tsx → /users/:userId routes/users/$userId/posts.tsx → /users/:userId/posts
Route Parameters
Dynamic Routes
// routes/posts/$postId.tsx import { createFileRoute } from '@tanstack/react-router'; import { postsApi } from '~/features/posts/api/postsApi'; export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { const post = await postsApi.get(params.postId); return { post }; }, component: PostDetails, }); function PostDetails() { const { post } = Route.useLoaderData(); const { postId } = Route.useParams(); return ( <div> <h1>{post.title}</h1> <p>{post.content}</p> </div> ); }
Multiple Parameters
// routes/users/$userId/posts/$postId.tsx export const Route = createFileRoute('/users/$userId/posts/$postId')({ loader: async ({ params }) => { const { userId, postId } = params; const post = await postsApi.getByUserAndId(userId, postId); return { post }; }, component: UserPostDetails, });
Route Loaders
Basic Loader
export const Route = createFileRoute('/posts')({ loader: async () => { const posts = await postsApi.getAll(); return { posts }; }, component: PostsPage, });
Loader with Dependencies
export const Route = createFileRoute('/users/$userId/posts')({ loader: async ({ params, context }) => { const [user, posts] = await Promise.all([ userApi.get(params.userId), postsApi.getByUser(params.userId), ]); return { user, posts }; }, component: UserPosts, });
Loader Error Handling
export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { try { const post = await postsApi.get(params.postId); return { post, error: null }; } catch (error) { return { post: null, error: 'Post not found' }; } }, component: PostDetails, }); function PostDetails() { const { post, error } = Route.useLoaderData(); if (error) return <Error message={error} />; return <div>{post.title}</div>; }
Navigation
import { Link, useNavigate } from '@tanstack/react-router'; // Link component <Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link> // Programmatic navigation const navigate = useNavigate(); navigate({ to: '/posts', search: { filter: 'published' } });
Lazy Loading
Lazy Route Component
// routes/posts/index.tsx import { createFileRoute } from '@tanstack/react-router'; import { lazy } from 'react'; const PostsPage = lazy(() => import('~/features/posts/PostsPage')); export const Route = createFileRoute('/posts')({ component: PostsPage, });
Lazy Loader
export const Route = createFileRoute('/posts')({ loader: async () => { // Dynamically import heavy module only when route loads const { processData } = await import('~/lib/heavyModule'); const posts = await postsApi.getAll(); const processed = processData(posts); return { posts: processed }; }, component: PostsPage, });
Search Params
Type-Safe Search Params
import { z } from 'zod'; const postsSearchSchema = z.object({ filter: z.enum(['all', 'published', 'draft']).default('all'), sort: z.enum(['date', 'title']).default('date'), page: z.number().default(1), }); export const Route = createFileRoute('/posts')({ validateSearch: postsSearchSchema, loader: async ({ search }) => { const posts = await postsApi.getAll(search); return { posts }; }, component: PostsPage, }); function PostsPage() { const { posts } = Route.useLoaderData(); const search = Route.useSearch(); return ( <div> <p>Filter: {search.filter}</p> <p>Sort: {search.sort}</p> <p>Page: {search.page}</p> </div> ); }
Updating Search Params
import { useNavigate } from '@tanstack/react-router'; function FilterButtons() { const navigate = useNavigate(); const search = Route.useSearch(); const setFilter = (filter: string) => { navigate({ to: '.', search: (prev) => ({ ...prev, filter }), }); }; return ( <div> <button onClick={() => setFilter('all')}>All</button> <button onClick={() => setFilter('published')}>Published</button> <button onClick={() => setFilter('draft')}>Draft</button> </div> ); }
Layouts
Root Layout
// routes/__root.tsx import { createRootRoute, Outlet } from '@tanstack/react-router'; export const Route = createRootRoute({ component: RootLayout, }); function RootLayout() { return ( <div> <Header /> <main> <Outlet /> {/* Child routes render here */} </main> <Footer /> </div> ); }
Nested Layouts
// routes/dashboard.tsx export const Route = createFileRoute('/dashboard')({ component: DashboardLayout, }); function DashboardLayout() { return ( <div className="dashboard"> <Sidebar /> <div className="content"> <Outlet /> {/* Dashboard child routes */} </div> </div> ); } // routes/dashboard/index.tsx export const Route = createFileRoute('/dashboard')({ component: DashboardHome, }); // routes/dashboard/analytics.tsx export const Route = createFileRoute('/dashboard/analytics')({ component: Analytics, });
Route Guards
Authentication Guard
export const Route = createFileRoute('/admin')({ beforeLoad: async ({ context }) => { if (!context.auth.isAuthenticated) { throw redirect({ to: '/login', search: { redirect: '/admin' }, }); } }, component: AdminPage, });
Permission Guard
export const Route = createFileRoute('/admin/users')({ beforeLoad: async ({ context }) => { if (!context.auth.hasPermission('users:manage')) { throw redirect({ to: '/unauthorized' }); } }, component: UsersPage, });
Breadcrumbs
Route Breadcrumbs
export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { const post = await postsApi.get(params.postId); return { post }; }, meta: ({ loaderData }) => [ { title: 'Home', path: '/' }, { title: 'Posts', path: '/posts' }, { title: loaderData.post.title, path: `/posts/${loaderData.post.id}` }, ], component: PostDetails, });
Best Practices
1. Use Loaders for Data
// ✅ Good: Loader fetches data export const Route = createFileRoute('/posts')({ loader: async () => { const posts = await postsApi.getAll(); return { posts }; }, component: PostsPage, }); // ❌ Avoid: Fetching in component function PostsPage() { const [posts, setPosts] = useState([]); useEffect(() => { postsApi.getAll().then(setPosts); }, []); return <div>...</div>; }
2. Lazy Load Heavy Routes
// ✅ Good: Lazy load admin panel const AdminPanel = lazy(() => import('~/features/admin/AdminPanel')); export const Route = createFileRoute('/admin')({ component: AdminPanel, });
3. Type-Safe Navigation
// ✅ Good: Type-safe Link <Link to="/posts/$postId" params={{ postId: post.id }}> View Post </Link> // ❌ Avoid: String concatenation <a href={`/posts/${post.id}`}>View Post</a>
Additional Resources
For more patterns, see:
- routing-guide.md - Advanced routing
- navigation-patterns.md - Navigation strategies
- route-loaders.md - Complex loaders