path-params
Dynamic path segments ($paramName), splat routes ($ / _splat), optional params ({-$paramName}), prefix/suffix patterns ({$param}.ext), useParams, params.parse/stringify, pathParamsAllowedCharacters, i18n locale patterns.
How do I install this agent skill?
npx skills add https://github.com/tanstack/router --skill path-paramsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides comprehensive documentation and code examples for managing dynamic path parameters in TanStack Router. It details best practices for type-safe routing, optional parameters, and splat routes without any security concerns.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Path Params
Path params capture dynamic URL segments into named variables. They are defined with a $ prefix in the route path.
CRITICAL: Never interpolate params into the
tostring. Always use theparamsprop. This is the most common agent mistake for path params.
CRITICAL: Types are fully inferred. Never annotate the return of
useParams().
Dynamic Segments
A segment prefixed with $ captures text until the next /.
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
// params.postId is string — fully inferred, do not annotate
return fetchPost(params.postId)
},
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
const data = Route.useLoaderData()
return (
<h1>
Post {postId}: {data.title}
</h1>
)
}
Multiple dynamic segments work across path levels:
// src/routes/teams.$teamId.members.$memberId.tsx
export const Route = createFileRoute('/teams/$teamId/members/$memberId')({
component: MemberComponent,
})
function MemberComponent() {
const { teamId, memberId } = Route.useParams()
return (
<div>
Team {teamId}, Member {memberId}
</div>
)
}
Splat / Catch-All Routes
A route with a path ending in $ (bare dollar sign) captures everything after it. The value is available under the _splat key.
// src/routes/files.$.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/files/$')({
component: FileViewer,
})
function FileViewer() {
const { _splat } = Route.useParams()
// URL: /files/documents/report.pdf → _splat = "documents/report.pdf"
return <div>File path: {_splat}</div>
}
Optional Params
Optional params use {-$paramName} syntax. The segment may or may not be present. When absent, the value is undefined.
// src/routes/posts.{-$category}.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/{-$category}')({
component: PostsComponent,
})
function PostsComponent() {
const { category } = Route.useParams()
// URL: /posts → category is undefined
// URL: /posts/tech → category is "tech"
return <div>{category ? `Posts in ${category}` : 'All Posts'}</div>
}
Multiple optional params:
// Matches: /posts, /posts/tech, /posts/tech/hello-world
export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({
component: PostComponent,
})
i18n with Optional Locale
// src/routes/{-$locale}/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/{-$locale}/about')({
component: AboutComponent,
})
function AboutComponent() {
const { locale } = Route.useParams()
const currentLocale = locale || 'en'
return <h1>{currentLocale === 'fr' ? 'À Propos' : 'About Us'}</h1>
}
// Matches: /about, /en/about, /fr/about
Prefix and Suffix Patterns
Curly braces {} around $paramName allow text before or after the dynamic part within a single segment.
Prefix
// src/routes/posts/post-{$postId}.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/post-{$postId}')({
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
// URL: /posts/post-123 → postId = "123"
return <div>Post ID: {postId}</div>
}
Suffix
// src/routes/files/{$fileName}[.]txt.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/files/{$fileName}.txt')({
component: FileComponent,
})
function FileComponent() {
const { fileName } = Route.useParams()
// URL: /files/readme.txt → fileName = "readme"
return <div>File: {fileName}.txt</div>
}
Combined Prefix + Suffix
// URL: /users/user-456.json → userId = "456"
export const Route = createFileRoute('/users/user-{$userId}.json')({
component: UserComponent,
})
function UserComponent() {
const { userId } = Route.useParams()
return <div>User: {userId}</div>
}
Navigating with Path Params
Object Form
import { Link } from '@tanstack/react-router'
function PostLink({ postId }: { postId: string }) {
return (
<Link to="/posts/$postId" params={{ postId }}>
View Post
</Link>
)
}
Function Form (Preserves Other Params)
function PostLink({ postId }: { postId: string }) {
return (
<Link to="/posts/$postId" params={(prev) => ({ ...prev, postId })}>
View Post
</Link>
)
}
Programmatic Navigation
import { useNavigate } from '@tanstack/react-router'
function GoToPost({ postId }: { postId: string }) {
const navigate = useNavigate()
return (
<button
onClick={() => {
navigate({ to: '/posts/$postId', params: { postId } })
}}
>
Go to Post
</button>
)
}
Navigating with Optional Params
// Include the optional param
<Link to="/posts/{-$category}" params={{ category: 'tech' }}>
Tech Posts
</Link>
// Omit the optional param (renders /posts)
<Link to="/posts/{-$category}" params={{ category: undefined }}>
All Posts
</Link>
Reading Params Outside Route Components
useParams with from
import { useParams } from '@tanstack/react-router'
function PostHeader() {
const { postId } = useParams({ from: '/posts/$postId' })
return <h2>Post {postId}</h2>
}
useParams with strict: false
function GenericBreadcrumb() {
const params = useParams({ strict: false })
// params is a union of all possible route params
return <span>{params.postId ?? 'Home'}</span>
}
Params in Loaders and beforeLoad
export const Route = createFileRoute('/posts/$postId')({
beforeLoad: async ({ params }) => {
// params.postId available here
const canView = await checkPermission(params.postId)
if (!canView) throw redirect({ to: '/unauthorized' })
},
loader: async ({ params }) => {
return fetchPost(params.postId)
},
})
Allowed Characters
By default, params are encoded with encodeURIComponent. Allow extra characters via router config:
import { createRouter } from '@tanstack/react-router'
const router = createRouter({
routeTree,
pathParamsAllowedCharacters: ['@', '+'],
})
Allowed characters: ;, :, @, &, =, +, $, ,.
Common Mistakes
1. CRITICAL (cross-skill): Interpolating path params into to string
// WRONG — breaks type safety and param encoding
<Link to={`/posts/${postId}`}>Post</Link>
// CORRECT — use params prop
<Link to="/posts/$postId" params={{ postId }}>Post</Link>
2. MEDIUM: Using * for splat routes instead of $
TanStack Router uses $ for splat routes. The captured value is under _splat, not *.
// WRONG (React Router / other frameworks)
// <Route path="/files/*" />
// CORRECT (TanStack Router)
// File: src/routes/files.$.tsx
export const Route = createFileRoute('/files/$')({
component: () => {
const { _splat } = Route.useParams()
return <div>{_splat}</div>
},
})
Note:
*works in v1 for backwards compatibility but will be removed in v2. Always use_splat.
3. MEDIUM: Using curly braces for basic dynamic segments
Curly braces are ONLY for prefix/suffix patterns and optional params. Basic dynamic segments use bare $.
// WRONG — braces not needed for basic params
createFileRoute('/posts/{$postId}')
// CORRECT — bare $ for basic dynamic segments
createFileRoute('/posts/$postId')
// CORRECT — braces for prefix pattern
createFileRoute('/posts/post-{$postId}')
// CORRECT — braces for optional param
createFileRoute('/posts/{-$category}')
4. Params are always strings
Path params are always parsed as strings. If you need a number, parse in the loader or component:
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const id = parseInt(params.postId, 10)
if (isNaN(id)) throw notFound()
return fetchPost(id)
},
})
You can also use params.parse and params.stringify on the route for bidirectional transformation:
export const Route = createFileRoute('/posts/$postId')({
params: {
parse: (raw) => ({ postId: parseInt(raw.postId, 10) }),
stringify: (parsed) => ({ postId: String(parsed.postId) }),
},
loader: async ({ params }) => {
// params.postId is now number
return fetchPost(params.postId)
},
})
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/tanstack/router/path-params">View path-params on skillZs</a>