Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Tanstack Router

ASecurity

TanStack Router v1 for React — file-based routing, type-safe navigation, validated search params, loaders and router context. Use when working in a project that uses `@tanstack/react-router` — routeTree.gen.ts, createFileRoute, Link/useNavigate, validateSearch, loaderDeps, route masking, or code splitting.

3 stars
0 votes
0 copies
0 views
Added 9/23/2026
ai-agentsrustgoreactexpressapi

Works with

cliapi

Security Analysis

A100/100

Pro scans all 6 files and shows the line behind each finding

Scanned 9/23/2026

$npx -y skills add Firzus/agent-skills --skill tanstack-router --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tanstack Router?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Tanstack Router
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/firzus-tanstack-router/badge)](https://www.skillsdirectory.com/skills/firzus-tanstack-router)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: tanstack-router
description: >-
  TanStack Router v1 for React — file-based routing, type-safe navigation,
  validated search params, loaders and router context. Use when working in a
  project that uses `@tanstack/react-router` — routeTree.gen.ts, createFileRoute,
  Link/useNavigate, validateSearch, loaderDeps, route masking, or code splitting.
---

# TanStack Router

Reference for TanStack Router v1 with React. This skill covers **client-side
routing only**. For the full-stack framework built on top of this router —
server routes, server functions, SSR wiring, deployment — use the sibling
`tanstack-start` skill. Everything here still applies inside a Start app: Start
uses this router underneath.

Prefer the project's existing route layout, validation library, and data layer;
apply these rules on top so route identity, type inference, and cache keys stay
predictable.

Branch-specific references, loaded on demand:

- [file-routing.md](./file-routing.md) — naming conventions, the generated route
  tree, layouts, groups, splat and optional params.
- [search-params.md](./search-params.md) — `validateSearch`, schema adapters,
  search middlewares, reading and writing search state.
- [data-loading.md](./data-loading.md) — loaders, `loaderDeps`, caching options,
  router context, pending/error/not-found components, deferred data.
- [navigation.md](./navigation.md) — `Link`, `useNavigate`, relative paths,
  `linkOptions`, preloading, route masking.
- [tanstack-query.md](./tanstack-query.md) — router as coordinator, the SSR query
  integration, and the split of responsibilities with TanStack Query.

## First Checks

1. Confirm `@tanstack/react-router` is installed and read the lockfile version.
   This skill targets v1.
2. Find the router instance (usually `src/router.tsx`). Read `createRouter` for
   `defaultPreload`, `defaultPreloadStaleTime`, `defaultStaleTime`,
   `notFoundMode`, and `routeMasks` — these change the correct advice for
   loaders and links.
3. Confirm the `Register` declaration merging block exists. Without it, top-level
   `Link`, `useNavigate`, and `useSearch` lose all route types.
4. Check whether routes come from the file-based generator (`routeTree.gen.ts`
   plus the bundler plugin) or from code-based `createRoute` calls. Never edit
   `routeTree.gen.ts` by hand.
5. Check the bundler config for `tanstackRouter({ autoCodeSplitting: true })` —
   it decides whether `.lazy.tsx` files are needed at all.
6. If a `queryClient` sits in the router context, treat the router as a
   coordinator, not a cache ([tanstack-query.md](./tanstack-query.md)).

## Registering The Router

Type safety across the whole project depends on two things: the generated route
tree, and declaration merging.

```tsx
// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
})

declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}
```

Without the `Register` block, every bare import from the library falls back to
loose types and autocomplete for `to`, `params`, and `search` disappears.

## Anatomy Of A Route

Every non-root route is created with `createFileRoute`, whose single string
argument is **written and maintained by the generator**. Do not hand-edit it;
move or rename the file and let the plugin update it.

```tsx
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  loader: ({ params }) => fetchPost(params.postId),
  component: PostComponent,
})

function PostComponent() {
  const { postId } = Route.useParams()
  const post = Route.useLoaderData()

  return <h1>{post.title}</h1>
}
```

The root route is different: `createRootRoute()`, or
`createRootRouteWithContext<T>()()` when the router carries context. It has no
path, is always matched, and its component always renders. It does **not**
support code splitting.

The `Route` object exposes typed hooks — `useParams`, `useSearch`,
`useLoaderData`, `useLoaderDeps`, `useMatch`, `useRouteContext`. Use them instead
of the bare exports whenever you are inside the route's own component.

## File Naming, In One Table

Full detail in [file-routing.md](./file-routing.md); this is the working subset.

| Token | Meaning |
|-------|---------|
| `__root.tsx` | Root route file, at the top of `routesDirectory`. |
| `.` | Nesting separator — `posts.$postId.tsx` nests under `posts.tsx`. |
| `$label` | Dynamic segment, captured into `params.label`. |
| `$` alone | Splat route; the rest of the pathname lands in `params._splat`. |
| `{-$label}` | Optional segment — matches with or without it. |
| `_` prefix | Pathless layout route: wraps children without adding a path. |
| `_` suffix | Non-nested route: escapes its parent's component tree. |
| `-` prefix | Excluded from the route tree; use it to colocate components. |
| `(folder)` | Route group — organizational only, contributes no path segment. |
| `index` | Matches the parent path exactly. |
| `route.tsx` | The route file for its containing directory. |
| `[x]` | Escapes a special character — `script[.]js.tsx` becomes `/script.js`. |

Directory nesting and `.` nesting are interchangeable and mixable. Choose per
subtree: directories for wide hierarchies, dots for a few deep ones.

## Type-Safe Navigation

Every navigation is relative and has both an origin (`from`) and a destination
(`to`). Without `from`, the router assumes the root and only autocompletes
absolute paths.

```tsx
<Link to="/posts/$postId" params={{ postId: '123' }} hash="comments">
  Post 123
</Link>
```

Rules that prevent most navigation bugs:

- Never interpolate values into `to`. Path params go in `params`, query state in
  `search`, the fragment in `hash`. `to` stays a literal route pattern.
- `params` and `search` accept a function of the previous value — use it to
  preserve existing state instead of rebuilding it.
- `to="."` reloads the current (or `from`) location; `to=".."` goes to the
  parent. Both are relative to `from` when supplied.
- Prefer `<Link>` for anything clickable — it renders a real `<a href>`, so
  cmd/ctrl-click and middle-click work. Reach for `useNavigate` only for
  side-effect navigation, such as after a successful submit.
- `redirect()` thrown from `beforeLoad` is the right tool for guards; a
  client-side navigation is not a substitute for a server redirect.
- Pass `from={Route.fullPath}` rather than a hand-written string, so refactors
  follow.

Active state is available three ways: `activeProps`/`inactiveProps`, the
`data-status="active"` attribute, and a function child receiving `{ isActive }`.
By default a link is active on **pathname prefix** match with search params
compared inclusively; pass `activeOptions={{ exact: true }}` for a home link.

See [navigation.md](./navigation.md) for `linkOptions`, preloading strategies,
and route masking.

## Search Params Are Typed State

TanStack Router parses the query string into **JSON**, not flat strings, and
`validateSearch` is the boundary that turns it into a type your app can trust.
The resulting type flows into the route's own options *and every child route*.

```tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().catch(1),
  filter: z.string().catch(''),
  sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
})

export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
  component: ProductList,
})

function ProductList() {
  const { page, filter, sort } = Route.useSearch()
  return <div>...</div>
}
```

Key points:

- `validateSearch` accepts a function or any object with a `parse` method, so a
  schema can be passed directly.
- Malformed params come from user-editable text. Prefer a fallback (`.catch()`)
  over a hard failure unless an error screen is genuinely the right UX — a throw
  triggers `onError` with `error.routerCode` set to `VALIDATE_SEARCH` and renders
  the `errorComponent`.
- With Zod v3 and `.default()`, `search` becomes *required* on every `Link` to
  that route. Use `@tanstack/zod-adapter`'s `zodValidator` (plus its `fallback`
  helper) to keep input and output types distinct. Zod v4, Valibot, ArkType, and
  Effect/Schema implement Standard Schema and need no adapter.
- Write search params with `<Link search={prev => ...}>` or
  `navigate({ search })`. From a component shared across routes, use `to="."` or
  `strict: false` to opt into looser types deliberately.

## Loaders, Context, Dependencies

A loader runs before the route renders, in parallel with sibling loaders, and
its result is cached under the route's parsed pathname **plus** whatever
`loaderDeps` returns.

```tsx
export const Route = createFileRoute('/posts')({
  validateSearch: z.object({ offset: z.number().catch(0) }),
  loaderDeps: ({ search: { offset } }) => ({ offset }),
  loader: ({ deps: { offset }, context, abortController }) =>
    context.fetchPosts({ offset, signal: abortController.signal }),
})
```

- Search params are deliberately absent from `loader` arguments. Anything the
  loader reads from search must pass through `loaderDeps`, otherwise the cache
  and preloading key on the wrong identity.
- Return only the deps you actually use. `loaderDeps: ({ search }) => search`
  reloads the route when any unrelated param changes.
- `loaderDeps` and `params.parse` run during route planning, possibly more than
  once. Keep them deterministic and side-effect-free.
- `beforeLoad` runs before the loader and before **all** child `beforeLoad`s. Its
  return value merges into the context for the route and its descendants. Throw
  `redirect()` here for guards, and re-throw redirects caught in a try/catch —
  `isRedirect(error)` tells them apart from real failures.
- Context is the dependency-injection channel: type it with
  `createRootRouteWithContext<T>()`, seed it in `createRouter({ context })`, and
  extend it per route via `beforeLoad`.

Defaults worth knowing: `staleTime` is `0` (stale immediately, revalidated in the
background), preloaded data is fresh for **30 s**, garbage collection windows are
**5 min**, the pending component appears after **1 s** and then stays for at
least **500 ms**. Full options in [data-loading.md](./data-loading.md).

## Errors, Pending, Not Found

| Situation | Route option | Notes |
|-----------|--------------|-------|
| Loader threw | `errorComponent` | Receives `error` and `reset`. |
| Loader is slow | `pendingComponent` | Gated by `pendingMs` / `pendingMinMs`. |
| Resource missing | `notFoundComponent` | Throw `notFound()` from the loader. |
| Side-effect on failure | `onError` / `onCatch` | Logging, reporting. |

- After a **load** failure, prefer `router.invalidate()` over `reset()`: it
  reruns the loader and resets the boundary together.
- Throw `notFound()` in the loader, not in the component — that keeps loader data
  correctly typed and avoids a flicker.
- Inside `notFoundComponent`, `useLoaderData` may be undefined; `useParams`,
  `useSearch`, and `useRouteContext` remain safe.
- Only routes that render an `<Outlet />` can host a not-found boundary, so give
  the root route a `notFoundComponent` or the router a
  `defaultNotFoundComponent`.

## Code Splitting

The router splits critical config (path parsing, `validateSearch`, `beforeLoad`,
`loader`, context) from non-critical config (`component`, `errorComponent`,
`pendingComponent`, `notFoundComponent`). The loader is intentionally **not**
split: it is already an async boundary and it is the most valuable thing to
preload.

Prefer `autoCodeSplitting: true` in the bundler plugin. It requires file-based
routing with a supported bundler and does not work with the CLI alone. Otherwise
move the non-critical exports into a `<route>.lazy.tsx` file using
`createLazyFileRoute`, which accepts only those four component options.

When a component lives outside its route file, reach for `getRouteApi('/path')`
instead of importing the `Route` object — same typed hooks, no circular import.

## Review Checklist

- The router is registered via `declare module` declaration merging, and
  `routeTree.gen.ts` is generated, not hand-edited.
- Route file names express the intended tree: `_` for pathless layouts, `(...)`
  for grouping, `-` for colocated non-route files, `index` for exact matches.
- No value is interpolated into `to`; params, search, and hash use their own
  props, and `from={Route.fullPath}` is used for relative navigation.
- Every route reading query state defines `validateSearch`, with fallbacks for
  malformed input and an adapter where the validation library needs one.
- `loaderDeps` returns exactly the search values the loader consumes — no more.
- `beforeLoad` guards throw `redirect()` and re-throw caught redirects via
  `isRedirect`; the guard is not treated as an authorization boundary for data.
- `staleTime`, `preloadStaleTime`, and `gcTime` are set deliberately per route
  where the defaults do not fit; `defaultPreloadStaleTime: 0` when an external
  cache owns freshness.
- Error, pending, and not-found components exist at the levels that render an
  `<Outlet />`; loader-failure retries call `router.invalidate()`.
- Components outside their route file use `getRouteApi`, not a `Route` import.
- Shared components either narrow with `from`/`to` or opt into `strict: false`
  explicitly — never rely on the accidental union.
- Full-stack concerns (server routes, server functions, SSR setup) live in the
  `tanstack-start` skill, not here.

Attribution

FirzusFirzus
View sourceSee grades on GitHubMore from Firzus →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698621 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →