tanstack-router
Skill0 starsChanged 4 months agoMITView on GitHub
How to use TanStack Router for file-based routing. Use this whenever creating new route files, adding search params validation, implementing route guards, using loaders, or navigating programmatically. Covers createFileRoute, validateSearch with Zod, beforeLoad guards, loader prefetch, useSearch, useParams, useNavigate, and Link.
14 other projects use a version of this.
Adopted by
Most recently taken up by
- yuan-alex/cedartheir own version, first seen Nov 8, 2026
- louisbrulenaudet/monorepo-templatetheir own version, first seen Oct 29, 2026
- Firzus/agent-skillstheir own version, first seen Oct 23, 2026
- itsLeonB/agent-config-managertheir own version, first seen Oct 20, 2026
- malinskibeniamin/skillstheir own version, first seen Oct 17, 2026
- rockcookies/skillstheir own version, first seen Oct 13, 2026
Used with
Projects with it also use accessibility, research and 2 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
ElasticSkillaccessibility
Accessibility guidance for Kibana. Use this skill when working with or reviewing EUI components, resolving a11y-related (@elastic/eui) ESLint issues, and ensuring proper use of ARIA attributes, focus management, keyboard interactions, and accessible naming conventions.
Also in 3 projects that use this
NVIDIASkillresearch
Before designing a feature, research how comparable libraries solve the problem, check best practices, known pitfalls, and upstream changes. Use when starting a new feature, investigating an unfamiliar area, or the user asks to research something.
Also in 4 projects that use this
SentrySkillreview
Fixture review skill.
Also in 4 projects that use this
GoogleSkillimprove-codebase-architecture
Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
Also in 3 projects that use this
Other versions14 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 14
- Mindrally/skills★ 275same name
- TangleML/tangle-ui★ 104same name
- louisbrulenaudet/monorepo-template★ 19same name
- Firzus/agent-skills★ 4same name
- malinskibeniamin/skills★ 4same name
- dvaJi/argos★ 3same name
- Spardutti/claude-skills★ 3same name
- Aver005/agent-skills★ 2same name
- bm629/agent-skills★ 2same name
- lobomfz/skills★ 2same name
4 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository12 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- feature-architectureSkill
- frontend-auditSkill
- frontend-i18nSkill
- mui-themeSkill
- react-coreSkill
- react-standardSkill
- table-mrtSkill
- tanstack-formSkill
- tanstack-querySkill
- flutter-clean-arch-getxSkill
- flutter-flavorsSkill
The file
We split this skill into the rules it gives an agent
9 rules. 0 are common, 2 are distinctive, and 7 are very specific to this project.
0
Common
Most of the 11 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 10
Never manually edit routeTree.gen.ts — it is auto-generated.
- Very specific: line 14
Every beforeLoad on a layout route must return { breadcrumb: '...' } (except redirect-only routes that only throw redirect).
- Distinctive: line 18
Read the relevant file when you need it:
- Very specific: line 30
Commit it to git so tsc and CI type-check without running the dev server
- Very specific: line 88
Run gen-route before type-check in CI if routeTree.gen.ts is missing.
- Very specific: line 113
If generation fails on dev or build, check route files in src/routes/ for JSX/TS syntax errors — the generator parses every route file and …
- Distinctive: line 139
Search schemas belong in the feature's schema file — never inline in a route file.
- Very specific: line 157
Use fallback(z.number(), defaultValue).default(defaultValue) for fields that need a fallback when the URL param is missing or invalid.
- Very specific: line 314
Never use bare useNavigate(), useSearch({ from }), or useParams({ from }) inside the route component function — those are for feature …
“Common” is measured: kept by at least 6 in 10 of the 11 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Routing with TanStack Router
Core Invariants (always enforced — never violate)
- Never manually edit
routeTree.gen.ts— it is auto-generated; commit it, don't hand-edit it. - Routes are global — all route files live in
src/routes/. Never create route files inside feature folders. - Search schemas never inline in route files — always import them from
features/[feature]/schemas/[feature].schema.ts. - Inside a route component, always use
Route.useSearch(),Route.useNavigate(), andRoute.useParams()— never the bare hooks withoutfrom. - Every
beforeLoadon a layout route must return{ breadcrumb: '...' }(except redirect-only routes that onlythrow redirect).
References
Read the relevant file when you need it:
references/authenticated-routes.md— full auth guard setup, RouterContext,beforeLoadredirect, per-route permission guardreferences/navigation-blocking.md—useBlocker, custom dialog withwithResolver,Blockcomponent, options table
Route Tree Generation
Overview
routeTree.gen.tslives atsrc/core/router/routeTree.gen.ts— co-located with router setup incore/- Generated automatically on
npm run devby the Vite plugin; regenerated when files insrc/routes/change - Commit it to git so
tscand CI type-check without running the dev server
tsr.config.json (project root)
Shared config for both the Vite plugin and CLI:
{
"routesDirectory": "./src/routes",
"generatedRouteTree": "./src/core/router/routeTree.gen.ts",
"autoCodeSplitting": true
}The Vite plugin reads this file automatically from the project cwd. Inline tanstackRouter({...}) options override file values when both are set.
Vite plugin (dev — must come before react())
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
tanstackRouter({
target: 'react',
routesDirectory: './src/routes',
generatedRouteTree: './src/core/router/routeTree.gen.ts',
autoCodeSplitting: true,
}),
react(),
],
})
TanStackRouterViteis a deprecated alias — prefertanstackRouter.
On npm run dev, the plugin generates the route tree on startup and watches src/routes/ for changes.
package.json scripts and devDependencies
{
"scripts": {
"dev": "vite",
"gen-route": "tsr generate",
"build": "npm run gen-route && tsc && vite build",
"type-check": "tsc --noEmit"
},
"devDependencies": {
"@tanstack/router-plugin": "^1.48.0",
"@tanstack/router-cli": "^1.48.0"
}
}Run gen-route before type-check in CI if routeTree.gen.ts is missing. Requires @tanstack/router-cli.
CLI commands (via gen-route script or directly):
npm run gen-route # one-shot — use in build/CI
npx tsr watch # continuous watch without dev serverPlugin config options
tanstackRouter({
target: 'react',
routesDirectory: './src/routes',
generatedRouteTree: './src/core/router/routeTree.gen.ts',
autoCodeSplitting: true,
routeFileIgnorePrefix: '-',
quoteStyle: 'single',
semicolons: false,
})Troubleshooting
If generation fails on dev or build, check route files in src/routes/ for JSX/TS syntax errors — the generator parses every route file and errors block output.
Route File Naming
src/routes/
├── __root.tsx
├── _layout.tsx
└── _layout/
└── your-section/
└── your-feature/
├── index.tsx # /your-section/your-feature/
├── create.tsx # /your-section/your-feature/create
└── $featureId/
├── index.tsx # /your-section/your-feature/$featureId
└── edit.tsx # /your-section/your-feature/$featureId/edit_ prefix = pathless layout (no URL segment). $param = dynamic segment. Always use nested directories — never dot notation (flat routes).
Search Schema Pattern
Search schemas belong in the feature's schema file — never inline in a route file.
// features/your-feature/schemas/your-feature.schema.ts
import { fallback } from '@tanstack/zod-adapter'
export const yourFeatureSearchSchema = z.object({
page: fallback(z.number(), 1).default(1),
page_size: fallback(z.number(), 20).default(20),
search: z.string().optional(),
status: z.string().optional(),
sort_by: z.string().optional(),
sort_order: z.string().optional(),
})
export type YourFeatureSearch = z.infer<typeof yourFeatureSearchSchema>Use fallback(z.number(), defaultValue).default(defaultValue) for fields that need a fallback when the URL param is missing or invalid. Plain .optional() is fine for truly optional filters.
Wire up in the route file by importing and passing directly:
import { zodValidator } from '@tanstack/zod-adapter'
import { yourFeatureSearchSchema } from '@/features/your-feature/schemas/your-feature.schema'
validateSearch: zodValidator(yourFeatureSearchSchema)Creating Route Files
List Page
// src/routes/_layout/your-section/your-feature/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { zodValidator } from '@tanstack/zod-adapter'
import { yourFeatureSearchSchema } from '@/features/your-feature/schemas/your-feature.schema'
import { guardRoute } from '@/shared/utils/route-guard.util'
export const Route = createFileRoute('/_layout/your-section/your-feature/')({
validateSearch: zodValidator(yourFeatureSearchSchema),
beforeLoad: ({ context }) => {
guardRoute(context.user, ['feature-index'])
return { breadcrumb: 'breadcrumbs.features' }
},
component: FeaturePage,
})
function FeaturePage() {
const { t } = useTranslation()
const searchParams = Route.useSearch()
const navigate = Route.useNavigate()
const handlePageChange = (page: number) => {
navigate({ search: (prev) => ({ ...prev, page }) })
}
const handlePageSizeChange = (pageSize: number) => {
navigate({ search: (prev) => ({ ...prev, page: 1, page_size: pageSize }) })
}
return (
<Container maxWidth="xl" sx={{ p: 0 }}>
{/* header, filters, list */}
</Container>
)
}Create Page
// src/routes/_layout/your-section/your-feature/create.tsx
import { createFileRoute } from '@tanstack/react-router'
import { FeatureForm } from '@/features/your-feature/components/FeatureForm'
import { guardRoute } from '@/shared/utils/route-guard.util'
export const Route = createFileRoute('/_layout/your-section/your-feature/create')({
beforeLoad: ({ context }) => {
guardRoute(context.user, ['feature-add'])
return { breadcrumb: 'breadcrumbs.createFeature' }
},
component: FeatureCreatePage,
})
function FeatureCreatePage() {
return <FeatureForm />
}Detail Page (with loader prefetch)
// src/routes/_layout/your-section/your-feature/$featureId/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { FeatureDetail } from '@/features/your-feature/components/FeatureDetail'
import { YOUR_FEATURE_QUERY_KEYS } from '@/features/your-feature/constants/query-keys'
import { yourFeatureService } from '@/features/your-feature/services/your-feature.service'
import { guardRoute } from '@/shared/utils/route-guard.util'
export const Route = createFileRoute('/_layout/your-section/your-feature/$featureId/')({
beforeLoad: ({ context }) => {
guardRoute(context.user, ['feature-index'])
return { breadcrumb: 'breadcrumbs.featureDetail' }
},
loader: async ({ params, context }) => {
const id = Number(params.featureId)
await context.queryClient.ensureQueryData({
queryKey: YOUR_FEATURE_QUERY_KEYS.DETAIL(id),
queryFn: ({ signal }) => yourFeatureService.getById(id, signal),
})
},
component: FeatureDetailPage,
})
function FeatureDetailPage() {
const { featureId } = Route.useParams()
return <FeatureDetail featureId={Number(featureId)} />
}Edit Page
// src/routes/_layout/your-section/your-feature/$featureId/edit.tsx
import { createFileRoute } from '@tanstack/react-router'
import { FeatureEditForm } from '@/features/your-feature/components/FeatureEditForm'
import { YOUR_FEATURE_QUERY_KEYS } from '@/features/your-feature/constants/query-keys'
import { yourFeatureService } from '@/features/your-feature/services/your-feature.service'
import { guardRoute } from '@/shared/utils/route-guard.util'
export const Route = createFileRoute('/_layout/your-section/your-feature/$featureId/edit')({
beforeLoad: ({ context }) => {
guardRoute(context.user, ['feature-edit'])
return { breadcrumb: 'breadcrumbs.editFeature' }
},
loader: async ({ params, context }) => {
const id = Number(params.featureId)
await context.queryClient.ensureQueryData({
queryKey: YOUR_FEATURE_QUERY_KEYS.DETAIL(id),
queryFn: ({ signal }) => yourFeatureService.getById(id, signal),
})
},
component: FeatureEditPage,
})
function FeatureEditPage() {
const { featureId } = Route.useParams()
return <FeatureEditForm featureId={Number(featureId)} />
}Navigation Hooks
Inside a route file — always use Route.* variants
// search params
const searchParams = Route.useSearch()
// navigate
const navigate = Route.useNavigate()
navigate({ search: (prev) => ({ ...prev, page: newPage }) })
navigate({ to: '/section/feature/$featureId', params: { featureId: String(id) } })
navigate({ to: '/section/feature' })
// params
const { featureId } = Route.useParams()Never use bare useNavigate(), useSearch({ from }), or useParams({ from }) inside the route component function — those are for feature components outside the route file.
Inside feature components (outside route file)
// strict mode — preferred for single-route components
const searchParams = useSearch({ from: '/_layout/section/feature/' })
const { featureId } = useParams({ from: '/_layout/section/feature/$featureId/' })
// non-strict — use only when a component renders under multiple routes
const searchParams = useSearch({ strict: false })
// select — re-renders only when selection changes
const page = useSearch({ from: '/_layout/section/feature/', select: (s) => s.page })Link
<Button component={Link} to="/section/feature/create" variant="contained">Create</Button>
<Link to="/section/feature/$featureId" params={{ featureId: '123' }}>View</Link>Router Config
// src/core/router/index.tsx
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
import { queryClient } from '@/core/config/query-client.config'
import type { RouterContext } from '@/routes/__root'
export function createRouter() {
return createTanStackRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
context: {
queryClient,
user: undefined,
} as RouterContext,
})
}
declare module '@tanstack/react-router' {
interface Register { router: ReturnType<typeof createRouter> }
}Root Layout
_layout.tsx wraps all authenticated routes — Navbar, Sidebar, <Outlet />. Its beforeLoad handles auth redirects (see references/authenticated-routes.md).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

