routing

star 1

Guide for TanStack Router file-based routing, navigation, and route parameters.

ReillySteere By ReillySteere schedule Updated 1/17/2026

name: routing description: Guide for TanStack Router file-based routing, navigation, and route parameters.

Routing

Use this skill when adding routes, handling navigation, or working with URL parameters.

1. File-Based Routing

Route File Locations

This project uses TanStack Router with file-based routing:

src/ui/shared/routes/
├── __root.tsx          # Root layout (always renders)
├── index.tsx           # Home route (/)
├── about.tsx           # /about
├── experience.tsx      # /experience
├── projects.tsx        # /projects
├── blog.index.tsx      # /blog (list view)
├── blog.$slug.tsx      # /blog/:slug (post detail)
└── blog.create.tsx     # /blog/create (new post)

Route Generation

Routes are auto-generated into src/ui/routeTree.gen.ts.

⚠️ CRITICAL: Never edit routeTree.gen.ts directly!

It regenerates automatically when you:

  • Add/remove/rename route files
  • Run npm start (watch mode)
  • Run npx tsr generate

Route File Structure

// src/ui/shared/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router';
import { AboutContainer } from 'ui/containers/about/about.container';

export const Route = createFileRoute('/about')({
  component: AboutContainer,
});

2. Route Patterns

Static Routes

Simple, no parameters:

// src/ui/shared/routes/about.tsx
export const Route = createFileRoute('/about')({
  component: AboutContainer,
});
// Matches: /about

Dynamic Routes (Parameters)

Use $ prefix for path parameters:

// src/ui/shared/routes/blog.$slug.tsx
import { createFileRoute } from '@tanstack/react-router';
import { BlogPostContainer } from 'ui/containers/blog/blog-post.container';

export const Route = createFileRoute('/blog/$slug')({
  component: BlogPostContainer,
});
// Matches: /blog/my-first-post, /blog/another-article

Accessing Parameters:

import { useParams } from '@tanstack/react-router';

function BlogPostContainer() {
  const { slug } = useParams({ from: '/blog/$slug' });
  // slug is typed as string

  const query = useBlogPost(slug);
  // ...
}

Nested Routes (Parent/Child)

Parent routes render children via <Outlet />:

// src/ui/shared/routes/blog.tsx (parent)
import { createFileRoute, Outlet } from '@tanstack/react-router';

export const Route = createFileRoute('/blog')({
  component: BlogLayout,
});

function BlogLayout() {
  return (
    <div>
      <h1>Blog</h1>
      <Outlet />  {/* Child routes render here */}
    </div>
  );
}
// src/ui/shared/routes/blog.index.tsx (default child)
export const Route = createFileRoute('/blog/')({
  component: BlogListContainer,
});
// Matches: /blog (exact)

// src/ui/shared/routes/blog.$slug.tsx (dynamic child)
export const Route = createFileRoute('/blog/$slug')({
  component: BlogPostContainer,
});
// Matches: /blog/my-post

Catch-All Routes

// src/ui/shared/routes/$.tsx
export const Route = createFileRoute('/$')({
  component: NotFound,
});
// Matches: any unmatched route

3. Navigation

Link Component (Declarative)

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

// Simple link
<Link to="/about">About</Link>

// With path parameters
<Link to="/blog/$slug" params={{ slug: 'my-post' }}>
  Read More
</Link>

// With search parameters
<Link to="/blog" search={{ category: 'tech', page: 1 }}>
  Tech Posts
</Link>

// Active link styling
<Link
  to="/about"
  activeProps={{ className: 'active' }}
  inactiveProps={{ className: 'inactive' }}
>
  About
</Link>

Programmatic Navigation

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

function CreatePostForm() {
  const navigate = useNavigate();

  const handleSubmit = async (data: FormData) => {
    const post = await createPost(data);

    // Navigate after success
    navigate({ to: '/blog/$slug', params: { slug: post.slug } });
  };

  // Go back
  const handleCancel = () => {
    navigate({ to: '..' }); // Parent route
    // or: history.back()
  };
}

Navigation with Search Params

// Reading search params
import { useSearch } from '@tanstack/react-router';

function BlogList() {
  const { category, page } = useSearch({ from: '/blog/' });
  // Use for filtering/pagination
}

// Setting search params
const navigate = useNavigate();
navigate({
  to: '/blog',
  search: { category: 'tech', page: 2 },
});

// Updating search params (preserve others)
navigate({
  to: '/blog',
  search: (prev) => ({ ...prev, page: prev.page + 1 }),
});

4. Route Data Loading

Loaders (Prefetch Data)

// src/ui/shared/routes/blog.$slug.tsx
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/blog/$slug')({
  loader: async ({ params }) => {
    // This runs before component renders
    return fetchBlogPost(params.slug);
  },
  component: BlogPost,
});

function BlogPost() {
  const post = Route.useLoaderData();
  // Data is already available, no loading state needed
}

Combining with TanStack Query

For this project, prefer TanStack Query over route loaders for server data:

// ✅ Recommended: TanStack Query for data fetching
export const Route = createFileRoute('/blog/$slug')({
  component: BlogPostContainer,
});

function BlogPostContainer() {
  const { slug } = useParams({ from: '/blog/$slug' });
  const query = useBlogPost(slug);

  return (
    <QueryState query={query}>
      {(post) => <BlogPostView post={post} />}
    </QueryState>
  );
}

Why? TanStack Query provides caching, background refetching, and works with the QueryState component pattern.

5. Route Guards (Protected Routes)

Before Load Hook

export const Route = createFileRoute('/admin')({
  beforeLoad: async ({ context }) => {
    const { auth } = context;

    if (!auth.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: { redirect: '/admin' },
      });
    }
  },
  component: AdminDashboard,
});

With Auth Store

import { useAuthStore } from 'ui/shared/hooks/useAuthStore';

export const Route = createFileRoute('/admin')({
  beforeLoad: () => {
    const token = useAuthStore.getState().token;

    if (!token) {
      throw redirect({ to: '/' });
    }
  },
  component: AdminDashboard,
});

6. Pending & Error States

Pending Navigation

import { usePendingComponent } from '@tanstack/react-router';

export const Route = createFileRoute('/blog/$slug')({
  pendingComponent: () => <LoadingSpinner />,
  component: BlogPost,
});

Error Boundaries

export const Route = createFileRoute('/blog/$slug')({
  errorComponent: ({ error }) => (
    <div>
      <h2>Error loading post</h2>
      <p>{error.message}</p>
    </div>
  ),
  component: BlogPost,
});

7. Common Patterns

Active Link Detection

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

function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
  const router = useRouterState();
  const isActive = router.location.pathname === to;

  return (
    <Link to={to} className={isActive ? 'active' : ''}>
      {children}
    </Link>
  );
}

Breadcrumbs

import { useMatches } from '@tanstack/react-router';

function Breadcrumbs() {
  const matches = useMatches();

  return (
    <nav>
      {matches.map((match, i) => (
        <span key={match.id}>
          {i > 0 && ' / '}
          <Link to={match.pathname}>{match.pathname}</Link>
        </span>
      ))}
    </nav>
  );
}

404 Not Found

// src/ui/shared/routes/$.tsx
export const Route = createFileRoute('/$')({
  component: () => (
    <div>
      <h1>404 - Page Not Found</h1>
      <Link to="/">Go Home</Link>
    </div>
  ),
});

8. Testing Routes

See testing-workflow skill for full details. Quick reference:

import { render } from 'test-utils';
import { RouterProvider, createMemoryHistory } from '@tanstack/react-router';

// Test component at specific route
const history = createMemoryHistory({ initialEntries: ['/blog/test-slug'] });
render(<RouterProvider router={router} history={history} />);

// Assert navigation occurred
expect(screen.getByText('Test Post')).toBeInTheDocument();

9. Quick Reference

File Naming → URL Mapping

File Name URL Path
index.tsx /
about.tsx /about
blog.index.tsx /blog
blog.$slug.tsx /blog/:slug
blog.create.tsx /blog/create
$.tsx /* (catch-all)

Hooks Quick Reference

Hook Purpose
useParams Get path parameters
useSearch Get search query parameters
useNavigate Programmatic navigation
useRouterState Current router state
useMatches All matched route segments
useLoaderData Data from route loader
Install via CLI
npx skills add https://github.com/ReillySteere/DeveloperProfile --skill routing
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
ReillySteere
ReillySteere Explore all skills →