SuperAPI AI Directory

tanstack-router

Frontend & websites

View repository

Updated 10-16 02:35

About this skill

TanStack Router

File-Based Routing

routes/
├── __root.tsx        # Root layout (meta, providers)
├── index.tsx         # / (homepage)
├── _app.tsx          # Layout for /_app/* routes
├── _app/
│   ├── index.tsx     # /app
│   └── settings.tsx  # /app/settings
├── posts.tsx         # /posts (list)
├── posts/
│   └── $id.tsx       # /posts/:id (detail)
└── api/
    └── auth/$.tsx    # /api/auth/* (catch-all)

Route Definition

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

export const Route = createFileRoute('/posts/$id')({
  component: PostPage,
  loader: async ({ params }) => fetchPost(params.id),
  validateSearch: z.object({ tab: z.string().optional() }),
  errorComponent: PostError,
  pendingComponent: PostSkeleton,
});

function PostPage() {
  const post = Route.useLoaderData();
  const { tab } = Route.useSearch();
  const { id } = Route.useParams();
  return <Post data={post} />;
}

Route Context Pattern

// __root.tsx
import { createRootRouteWithContext } from '@tanstack/react-router';

type RouterContext = {
  queryClient: QueryClient;
};

export const Route = createRootRouteWithContext<RouterContext>()({
  component: RootLayout,
});

// router.tsx
export const getRouter = () =>
  createRouter({
    routeTree,
    context: { queryClient },
  });

Auth Guard (beforeLoad)

export const Route = createFileRoute('/_app')({
  beforeLoad: async ({ context }) => {
    const session = await auth.api.getSession({
      headers: context.request.headers,
    });

    if (!session) {
      throw redirect({ to: '/login' });
    }

    return { user: session.user };
  },
  component: AppLayout,
});

Navigation

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

// Link component
<Link to="/posts/$id" params={{ id: '123' }} search={{ tab: 'comments' }}>
  View Post
</Link>;

// Programmatic navigation
const navigate = useNavigate();
navigate({ to: '/posts', search: { page: 2 } });
navigate({ to: '..', search: (prev) => ({ ...prev }) }); // Relative

Search Params with Zod

import { zodValidator } from '@tanstack/zod-adapter';
import { stripSearchParams } from '@tanstack/react-router';

const defaults = { page: 1, sort: 'newest' as const };

const searchSchema = z.object({
  page: z.number().default(defaults.page),
  sort: z.enum(['newest', 'oldest']).default(defaults.sort),
  filter: z.string().optional(),
});

export const Route = createFileRoute('/posts')({
  validateSearch: zodValidator(searchSchema),
  search: { middlewares: [stripSearchParams(defaults)] },
  component: PostsPage,
});

function PostsPage() {
  const { page, sort, filter } = Route.useSearch();
  const navigate = useNavigate();

  const setPage = (newPage: number) => {
    navigate({ search: (prev) => ({ ...prev, page: newPage }) });
  };
}

Layout Routes

// _app.tsx - Pathless layout
export const Route = createFileRoute('/_app')({
  component: AppLayout,
});

function AppLayout() {
  return (
    <div className="flex">
      <Sidebar />
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
    </div>
  );
}

Loader with TanStack Query

export const Route = createFileRoute('/posts')({
  loader: async ({ context }) => {
    await context.queryClient.ensureQueryData(postsOptions());
  },
  component: PostsPage,
});

function PostsPage() {
  const { data } = useSuspenseQuery(postsOptions());
  return <PostList posts={data} />;
}

Common Mistakes

Mistake Correct Pattern
Using validateSearch without adapter Use zodValidator(schema)
Auth check in component Use beforeLoad for route guards
Not awaiting ensureQueryData Await to block navigation until ready
Forgetting Outlet in layouts Layout components need <Outlet />
Hardcoding paths in navigate Use type-safe to with params
Missing $ prefix for dynamic routes Use $id.tsx not id.tsx
Route params without validation Validate with Zod in loader
Not using search middleware Use stripSearchParams for clean URLs
Circular redirects in beforeLoad Check current location before redirect
Not handling 404 in loader Use throw notFound() for missing data

Delegation

  • Query patterns: For data fetching in loaders, see tanstack-query skill
  • Server functions: For API calls, see tanstack-start skill
  • Auth patterns: For authentication, see auth skill
  • Code review: After implementing routes, delegate to code-reviewer agent

Topic References