Back to list
oakoss

tanstack-router

by oakoss

Open-source SaaS starter kit with React, TanStack, and Better Auth

0🍴 0📅 Jan 26, 2026

SKILL.md


name: tanstack-router description: TanStack Router file-based routing. Use for routes, params, search params, navigation, loaders, auth guards, layouts, nested routes, redirect

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

MistakeCorrect Pattern
Using validateSearch without adapterUse zodValidator(schema)
Auth check in componentUse beforeLoad for route guards
Not awaiting ensureQueryDataAwait to block navigation until ready
Forgetting Outlet in layoutsLayout components need <Outlet />
Hardcoding paths in navigateUse type-safe to with params
Missing $ prefix for dynamic routesUse $id.tsx not id.tsx
Route params without validationValidate with Zod in loader
Not using search middlewareUse stripSearchParams for clean URLs
Circular redirects in beforeLoadCheck current location before redirect
Not handling 404 in loaderUse 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

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon