tanstack-router

TanStack Router Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tanstack-router" with this command: npx skills add blencorp/claude-code-kit/blencorp-claude-code-kit-tanstack-router

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>; }

  1. Lazy Load Heavy Routes

// ✅ Good: Lazy load admin panel const AdminPanel = lazy(() => import('~/features/admin/AdminPanel'));

export const Route = createFileRoute('/admin')({ component: AdminPanel, });

  1. 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

tailwindcss

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

shadcn

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

nextjs

No summary provided by upstream source.

Repository SourceNeeds Review