一键导入
tanstack-router
TanStack Router file-based routing. Use for routes, params, search params, navigation, loaders, auth guards, layouts, nested routes, redirect
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
TanStack Router file-based routing. Use for routes, params, search params, navigation, loaders, auth guards, layouts, nested routes, redirect
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Git workflow for branches, commits, and PRs. Use for commit, branch, pr, pull request, conventional, push, feat, fix, chore, merge, rebase
Storybook stories and interaction tests. Use for story, stories, storybook, chromatic, visual test, interaction test, play function, component test
TypeScript and unicorn linting patterns. Use for typescript, array, for-of, reduce, forEach, throw, catch, modern js, es modules, string, number, error handling
Zod v4 schema validation. Use for zod, schema, validation, parse, safeParse, infer, coerce, transform, refine, z.object, z.string, z.email, z.url
Better Auth authentication. Use for auth, login, logout, session, user, signup, register, protect, middleware, password, oauth, social
Drizzle ORM + PostgreSQL database layer. Use for db, database, query, schema, table, migrate, sql, postgres, drizzle, model, relation
| name | tanstack-router |
| description | TanStack Router file-based routing. Use for routes, params, search params, navigation, loaders, auth guards, layouts, nested routes, redirect |
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)
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} />;
}
// __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 },
});
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
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 }) });
};
}
// _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>
);
}
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} />;
}
| 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 |
code-reviewer agent