| name | tanstack-start-typesafe-routing |
| description | Implement type-safe navigation and links in TanStack Start. Use when creating links, navigating programmatically, working with search params, or accessing route parameters with full TypeScript support. |
| license | Apache-2.0 |
| metadata | {"author":"tanstack","version":"1.0"} |
TanStack Start Type-Safe Routing
TanStack Router provides full type-safety for navigation, parameters, and search params. TypeScript catches invalid routes at compile time.
When to Use
- Creating links between pages
- Programmatic navigation
- Working with URL search parameters
- Accessing route parameters
- Building type-safe navigation components
Type-Safe Links
import { Link } from '@tanstack/react-router';
function Navigation() {
return (
<nav>
{/* Basic link */}
<Link to="/">Home</Link>
{/* Link with params - TypeScript enforces required params */}
<Link to="/posts/$postId" params={{ postId: '123' }}>
View Post
</Link>
{/* Link with search params */}
<Link to="/posts" search={{ page: 1, sort: 'newest' }}>
Posts
</Link>
{/* Link with both */}
<Link
to="/users/$userId/posts"
params={{ userId: 'abc' }}
search={{ filter: 'published' }}
>
User Posts
</Link>
</nav>
);
}
TypeScript Catches Errors
<Link to="/post">Post</Link>
<Link to="/posts/$postId">Post</Link>
<Link to="/posts/$postId" params={{ postid: '123' }}>Post</Link>
<Link to="/posts/$postId" params={{ postId: '123' }}>Post</Link>
Link Props
<Link
to="/posts/$postId"
params={{ postId: '123' }}
search={{ page: 1 }}
hash="comments"
replace
preload="intent"
activeProps={{ className: 'active' }}
inactiveProps={{ className: 'inactive' }}
activeOptions={{ exact: true }}
disabled={isLoading}
>
View Post
</Link>
Programmatic Navigation
useNavigate Hook
import { useNavigate } from '@tanstack/react-router';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate({ to: '/posts' });
navigate({
to: '/posts/$postId',
params: { postId: '123' },
});
navigate({
to: '/posts',
search: { page: 2, sort: 'date' },
});
navigate({
to: '/login',
replace: true,
});
navigate({
to: '..',
});
};
return <button onClick={handleClick}>Go</button>;
}
From Route Context
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
});
function PostComponent() {
const navigate = Route.useNavigate();
const goToEdit = () => {
navigate({
to: '/posts/$postId/edit',
params: (prev) => prev,
});
};
}
Search Parameters
Defining Search Schema
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
const PostsSearchSchema = z.object({
page: z.number().default(1),
sort: z.enum(['newest', 'oldest', 'popular']).default('newest'),
filter: z.string().optional(),
});
export const Route = createFileRoute('/posts')({
validateSearch: PostsSearchSchema,
component: PostsComponent,
});
function PostsComponent() {
const search = Route.useSearch();
return (
<div>
<p>Page: {search.page}</p>
<p>Sort: {search.sort}</p>
{search.filter && <p>Filter: {search.filter}</p>}
</div>
);
}
Updating Search Params
function PostsComponent() {
const search = Route.useSearch();
const navigate = useNavigate();
const setPage = (page: number) => {
navigate({
search: (prev) => ({ ...prev, page }),
});
};
const setSort = (sort: 'newest' | 'oldest' | 'popular') => {
navigate({
search: (prev) => ({ ...prev, sort, page: 1 }),
});
};
const clearFilter = () => {
navigate({
search: (prev) => {
const { filter, ...rest } = prev;
return rest;
},
});
};
return (
<div>
<select value={search.sort} onChange={(e) => setSort(e.target.value as any)}>
<option value="newest">Newest</option>
<option value="oldest">Oldest</option>
<option value="popular">Popular</option>
</select>
<button onClick={() => setPage(search.page + 1)}>Next Page</button>
</div>
);
}
useSearch Hook
import { useSearch } from '@tanstack/react-router';
function SearchDisplay() {
const search = useSearch({ from: '/posts' });
const strictSearch = useSearch({ from: '/posts', strict: true });
return <div>Page: {search.page}</div>;
}
Route Parameters
Accessing Params
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
});
function PostComponent() {
const params = Route.useParams();
return <h1>Post {params.postId}</h1>;
}
Multiple Params
export const Route = createFileRoute('/users/$userId/posts/$postId')({
component: UserPostComponent,
});
function UserPostComponent() {
const { userId, postId } = Route.useParams();
return <h1>User {userId}'s Post {postId}</h1>;
}
Typed Params with Parsing
export const Route = createFileRoute('/posts/$postId')({
parseParams: (params) => ({
postId: parseInt(params.postId, 10),
}),
stringifyParams: (params) => ({
postId: String(params.postId),
}),
component: PostComponent,
});
function PostComponent() {
const { postId } = Route.useParams();
console.log(postId + 1);
}
Building Type-Safe Navigation Components
Breadcrumbs
import { Link, useMatches } from '@tanstack/react-router';
function Breadcrumbs() {
const matches = useMatches();
return (
<nav aria-label="Breadcrumb">
<ol>
{matches.map((match, index) => {
const isLast = index === matches.length - 1;
return (
<li key={match.id}>
{isLast ? (
<span>{match.routeId}</span>
) : (
<Link to={match.pathname}>{match.routeId}</Link>
)}
</li>
);
})}
</ol>
</nav>
);
}
Active Link Styling
<Link
to="/posts"
activeProps={{
className: 'text-blue-600 font-bold',
}}
inactiveProps={{
className: 'text-gray-600',
}}
>
Posts
</Link>
<Link
to="/posts"
activeOptions={{ exact: true }}
activeProps={{ className: 'active' }}
>
Posts Index
</Link>
<Link
to="/posts"
search={{ sort: 'newest' }}
activeOptions={{ includeSearch: true }}
>
Newest Posts
</Link>
Router Hooks
import {
useRouter,
useRouterState,
useLocation,
useParams,
useSearch,
useNavigate,
useMatches,
} from '@tanstack/react-router';
function RouterInfo() {
const router = useRouter();
const state = useRouterState();
const location = useLocation();
console.log(location.pathname, location.search, location.hash);
const params = useParams({ from: '/posts/$postId' });
const search = useSearch({ from: '/posts' });
const navigate = useNavigate();
const matches = useMatches();
}
Type Registration
For full type safety, register your router:
import { createRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';
export function getRouter() {
return createRouter({
routeTree,
});
}
declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>;
}
}
Common Patterns
Preserving Search Params on Navigation
const navigate = useNavigate();
navigate({
to: '/posts/$postId',
params: { postId: '456' },
search: (prev) => prev,
});
navigate({
search: (prev) => ({
...prev,
page: 1,
}),
});
Conditional Navigation
const navigate = useNavigate();
if (isAuthorized) {
navigate({ to: '/admin' });
} else {
navigate({ to: '/login', search: { redirect: '/admin' } });
}
Back/Forward Navigation
const router = useRouter();
router.history.back();
router.history.forward();
router.history.go(-2);