← Back to list

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,
});
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-revieweragent
Topic References
- Route Context - Context inheritance, beforeLoad, nested layouts
- Search Params - zodValidator, stripSearchParams, URL state
- Navigation - Link, useNavigate, active states, type safety
- Loaders & Errors - Data loading, error boundaries, Suspense
- Advanced Features - Preloading, scroll restoration, code splitting
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

