diff --git a/README.md b/README.md index 63c6a2ff..f39be37f 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ By creating a `.cursorrules` file in your project's root directory, you can leve - [React (Redux, TypeScript)](./rules/react-redux-typescript-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with Redux and TypeScript integration. - [React (MobX)](./rules/react-mobx-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with MobX integration. - [React (React Query)](./rules/react-query-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with React Query integration. +- [React (TanStack Router)](./rules/tanstack-router-react-cursorrules-prompt-file/.cursorrules) - Cursor rules for React development with TanStack Router v1, including file-based routing, type-safe search params, loaders, and auth guards. ### Database and API diff --git a/rules-new/tanstack-router.mdc b/rules-new/tanstack-router.mdc new file mode 100644 index 00000000..a785244b --- /dev/null +++ b/rules-new/tanstack-router.mdc @@ -0,0 +1,99 @@ +--- +description: Type-safe routing with TanStack Router v1 for React apps, including file-based routing, loaders, search params validation, auth guards, and TanStack Query integration +globs: ["src/routes/**/*", "src/routeTree.gen.ts", "app.config.ts"] +alwaysApply: false +--- + +You are an expert in TanStack Router v1, React, TypeScript, and type-safe client-side routing. + +## Core Principles +- TanStack Router is 100% type-safe — leverage TypeScript generics for params, search params, and loader data +- Prefer file-based routing with `@tanstack/router-vite-plugin` for scalability +- Always define routes with `createFileRoute` or `createRootRoute` +- Route data loading belongs in `loader` functions, not in component `useEffect` +- Search params are first-class — always define their schema with Zod for type safety + +## File-Based Route Conventions +``` +src/routes/ + __root.tsx ← Root layout + index.tsx ← / route + posts/ + index.tsx ← /posts + $postId.tsx ← /posts/:postId (dynamic) + _layout.tsx ← Layout route (no path segment) + _auth/ ← Pathless auth layout group + dashboard.tsx +``` + +## Route Definition +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => fetchPost(params.postId), + component: PostComponent, + errorComponent: ({ error }) => , + pendingComponent: () => , +}) + +function PostComponent() { + const post = Route.useLoaderData() // type-safe + const { postId } = Route.useParams() // type-safe + return
{post.title}
+} +``` + +## Type-Safe Search Params +- Always define search params with Zod and `validateSearch` +- Access with `Route.useSearch()` — never read `window.location.search` directly +```tsx +const searchSchema = z.object({ + page: z.number().int().min(1).default(1), + q: z.string().optional(), +}) + +export const Route = createFileRoute('/search')({ + validateSearch: searchSchema, + component: SearchPage, +}) +``` + +## Navigation +- Use `` for internal navigation — never `` +- Always pass typed `params` and `search` — the compiler will catch mistakes +```tsx +View Post +``` + +## Loaders + TanStack Query Integration +```tsx +export const Route = createFileRoute('/posts')({ + loader: ({ context: { queryClient } }) => + queryClient.ensureQueryData(postsQueryOptions()), + component: PostsPage, +}) +``` + +## Router Context for Dependency Injection +```tsx +// __root.tsx +interface RouterContext { queryClient: QueryClient; auth: AuthState } +export const Route = createRootRouteWithContext()({ component: RootLayout }) + +// main.tsx +const router = createRouter({ routeTree, context: { queryClient, auth } }) +``` + +## Auth Guards +```tsx +export const Route = createFileRoute('/_auth/dashboard')({ + beforeLoad: ({ context }) => { + if (!context.auth.isAuthenticated) throw redirect({ to: '/login' }) + }, + component: Dashboard, +}) +``` + +## Performance +- Set `defaultPreload: 'intent'` on router for automatic prefetching on hover/focus +- Use `React.lazy` for route component code splitting +- Install `@tanstack/router-devtools` and render `` in development diff --git a/rules/tanstack-router-react-cursorrules-prompt-file/.cursorrules b/rules/tanstack-router-react-cursorrules-prompt-file/.cursorrules new file mode 100644 index 00000000..7ba8ea95 --- /dev/null +++ b/rules/tanstack-router-react-cursorrules-prompt-file/.cursorrules @@ -0,0 +1,170 @@ +You are an expert in TanStack Router, React, TypeScript, and modern type-safe client-side routing. + +# TanStack Router + React Guidelines + +## Core Philosophy +- TanStack Router is 100% type-safe — leverage TypeScript generics for route params, search params, and loader data +- Prefer file-based routing for scalability; use code-based routing only for highly dynamic use cases +- Always define routes with `createFileRoute` or `createRootRoute` — never use plain objects +- Route data loading belongs in `loader` functions, not in component `useEffect` +- Search params are first-class citizens — define their schema with Zod or Valibot for validation and type inference + +## Project Setup +- Use `@tanstack/react-router` with Vite and the `@tanstack/router-vite-plugin` for file-based routing +- Enable `routeTree.gen.ts` auto-generation — never manually edit this file +- Structure routes under `src/routes/` directory +- Root layout goes in `src/routes/__root.tsx` +- Use `src/routes/index.tsx` for the home/index route + +## File-Based Route Conventions +``` +src/routes/ + __root.tsx ← Root layout (wraps all routes) + index.tsx ← / route + about.tsx ← /about route + posts/ + index.tsx ← /posts route + $postId.tsx ← /posts/:postId (dynamic segment) + _layout.tsx ← Layout route (no path segment) + _auth/ + login.tsx ← /login (grouped under auth layout) + (admin)/ + dashboard.tsx ← /dashboard (pathless group) +``` + +## Route Definition Patterns +```tsx +// src/routes/posts/$postId.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => { + return fetchPost(params.postId) // fully typed params + }, + component: PostComponent, +}) + +function PostComponent() { + const post = Route.useLoaderData() // type-safe loader data + const { postId } = Route.useParams() // type-safe params + return
{post.title}
+} +``` + +## Type-Safe Search Params +- Always define search param schemas using `z.object()` from Zod +- Use `validateSearch` option on route definition +- Access with `Route.useSearch()` — never read raw `window.location.search` +```tsx +import { z } from 'zod' +import { createFileRoute } from '@tanstack/react-router' + +const searchSchema = z.object({ + page: z.number().int().min(1).default(1), + q: z.string().optional(), +}) + +export const Route = createFileRoute('/search')({ + validateSearch: searchSchema, + component: SearchPage, +}) + +function SearchPage() { + const { page, q } = Route.useSearch() + // ... +} +``` + +## Navigation +- Use `` from `@tanstack/react-router` — never `
` for internal navigation +- Use `useNavigate()` for programmatic navigation +- Always pass typed `params` and `search` to Link — the compiler will catch mistakes +```tsx +import { Link, useNavigate } from '@tanstack/react-router' + +// Declarative +View Post + +// Programmatic +const navigate = useNavigate() +navigate({ to: '/posts/$postId', params: { postId: post.id } }) +``` + +## Loaders & Data Fetching +- Use `loader` for data that must be available before render (no loading spinners for critical data) +- Integrate with TanStack Query by using `ensureQueryData` inside loaders for caching +- Use `staleTime` on loaders to avoid redundant fetches during navigation +- Return plain serializable data from loaders — no class instances +```tsx +export const Route = createFileRoute('/posts')({ + loader: ({ context: { queryClient } }) => + queryClient.ensureQueryData(postsQueryOptions()), + component: PostsPage, +}) +``` + +## Error Handling +- Define `errorComponent` on routes to handle loader or render errors +- Use `notFoundComponent` for 404 states within a route subtree +- Use `pendingComponent` for showing skeletons/spinners during data loading +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: fetchPost, + errorComponent: ({ error }) => , + pendingComponent: () => , + notFoundComponent: () => , + component: PostDetail, +}) +``` + +## Router Context +- Use router context to inject global dependencies (queryClient, auth, theme) into loaders +- Define context type in `__root.tsx` and pass it when creating the router +```tsx +// __root.tsx +import { createRootRouteWithContext } from '@tanstack/react-router' + +interface RouterContext { + queryClient: QueryClient + auth: AuthState +} + +export const Route = createRootRouteWithContext()({ + component: RootLayout, +}) + +// main.tsx +const router = createRouter({ + routeTree, + context: { queryClient, auth }, +}) +``` + +## Route Guards / Auth +- Use `beforeLoad` for authentication checks — redirect to login if unauthenticated +- Never put auth logic inside components — handle it at the routing layer +```tsx +export const Route = createFileRoute('/_auth/dashboard')({ + beforeLoad: ({ context }) => { + if (!context.auth.isAuthenticated) { + throw redirect({ to: '/login' }) + } + }, + component: Dashboard, +}) +``` + +## Performance +- Use `preload` on `` to trigger loader prefetching on hover/focus +- Set `defaultPreload: 'intent'` on the router for automatic preloading +- Use `gcTime` and `staleTime` on loaders to tune cache behavior +- Lazy-load route components with `React.lazy` for code splitting + +## DevTools +- Install `@tanstack/router-devtools` and render `` in development +- Use devtools to inspect route tree, active matches, loader data, and search params + +## Testing +- Use `createMemoryHistory` and `createRouter` to create isolated router instances in tests +- Wrap components under test with `` +- Mock loaders by providing fake context values diff --git a/rules/tanstack-router-react-cursorrules-prompt-file/README.md b/rules/tanstack-router-react-cursorrules-prompt-file/README.md new file mode 100644 index 00000000..5e5d8f43 --- /dev/null +++ b/rules/tanstack-router-react-cursorrules-prompt-file/README.md @@ -0,0 +1,19 @@ +# TanStack Router + React Cursor Rules + +Cursor rules for building type-safe React applications with TanStack Router v1, including file-based routing, type-safe search params, loaders, route context, auth guards, and integration with TanStack Query. + +## What's covered +- File-based routing conventions and directory structure +- Type-safe params and search param validation with Zod +- Loader-based data fetching (with TanStack Query integration) +- Navigation with `` and `useNavigate` +- Error, pending, and not-found components +- Router context for dependency injection +- Authentication guards with `beforeLoad` +- Code splitting and preloading strategies +- DevTools setup and testing patterns + +## Author +Created by [usm4nhafeez](https://github.com/usm4nhafeez) + +Contributed to [awesome-cursorrules](https://github.com/PatrickJS/awesome-cursorrules) diff --git a/rules/tanstack-router-react-cursorrules-prompt-file/tanstack-router.mdc b/rules/tanstack-router-react-cursorrules-prompt-file/tanstack-router.mdc new file mode 100644 index 00000000..a785244b --- /dev/null +++ b/rules/tanstack-router-react-cursorrules-prompt-file/tanstack-router.mdc @@ -0,0 +1,99 @@ +--- +description: Type-safe routing with TanStack Router v1 for React apps, including file-based routing, loaders, search params validation, auth guards, and TanStack Query integration +globs: ["src/routes/**/*", "src/routeTree.gen.ts", "app.config.ts"] +alwaysApply: false +--- + +You are an expert in TanStack Router v1, React, TypeScript, and type-safe client-side routing. + +## Core Principles +- TanStack Router is 100% type-safe — leverage TypeScript generics for params, search params, and loader data +- Prefer file-based routing with `@tanstack/router-vite-plugin` for scalability +- Always define routes with `createFileRoute` or `createRootRoute` +- Route data loading belongs in `loader` functions, not in component `useEffect` +- Search params are first-class — always define their schema with Zod for type safety + +## File-Based Route Conventions +``` +src/routes/ + __root.tsx ← Root layout + index.tsx ← / route + posts/ + index.tsx ← /posts + $postId.tsx ← /posts/:postId (dynamic) + _layout.tsx ← Layout route (no path segment) + _auth/ ← Pathless auth layout group + dashboard.tsx +``` + +## Route Definition +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => fetchPost(params.postId), + component: PostComponent, + errorComponent: ({ error }) => , + pendingComponent: () => , +}) + +function PostComponent() { + const post = Route.useLoaderData() // type-safe + const { postId } = Route.useParams() // type-safe + return
{post.title}
+} +``` + +## Type-Safe Search Params +- Always define search params with Zod and `validateSearch` +- Access with `Route.useSearch()` — never read `window.location.search` directly +```tsx +const searchSchema = z.object({ + page: z.number().int().min(1).default(1), + q: z.string().optional(), +}) + +export const Route = createFileRoute('/search')({ + validateSearch: searchSchema, + component: SearchPage, +}) +``` + +## Navigation +- Use `` for internal navigation — never `
` +- Always pass typed `params` and `search` — the compiler will catch mistakes +```tsx +View Post +``` + +## Loaders + TanStack Query Integration +```tsx +export const Route = createFileRoute('/posts')({ + loader: ({ context: { queryClient } }) => + queryClient.ensureQueryData(postsQueryOptions()), + component: PostsPage, +}) +``` + +## Router Context for Dependency Injection +```tsx +// __root.tsx +interface RouterContext { queryClient: QueryClient; auth: AuthState } +export const Route = createRootRouteWithContext()({ component: RootLayout }) + +// main.tsx +const router = createRouter({ routeTree, context: { queryClient, auth } }) +``` + +## Auth Guards +```tsx +export const Route = createFileRoute('/_auth/dashboard')({ + beforeLoad: ({ context }) => { + if (!context.auth.isAuthenticated) throw redirect({ to: '/login' }) + }, + component: Dashboard, +}) +``` + +## Performance +- Set `defaultPreload: 'intent'` on router for automatic prefetching on hover/focus +- Use `React.lazy` for route component code splitting +- Install `@tanstack/router-devtools` and render `` in development