React Router is great but has no type safety. TanStack Router gives you fully typed routes, search params, and loaders.
What is TanStack Router?
TanStack Router is a fully type-safe routing library for React. Every route path, every search parameter, every loader return type — all type-checked at compile time.
Quick Start
bun add @tanstack/react-router
bun add -d @tanstack/router-plugin @tanstack/router-devtools
File-Based Routing
src/routes/
__root.tsx # Root layout
index.tsx # /
about.tsx # /about
posts/
index.tsx # /posts
$postId.tsx # /posts/:postId
settings/
profile.tsx # /settings/profile
notifications.tsx # /settings/notifications
Root Layout
// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router';
export const Route = createRootRoute({
component: () => (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/posts">Posts</Link>
<Link to="/about">About</Link>
</nav>
<Outlet />
</div>
),
});
Route with Loader
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
// params.postId is typed as string!
const res = await fetch(`/api/posts/${params.postId}`);
return res.json() as Promise<Post>;
},
component: PostPage,
});
function PostPage() {
const post = Route.useLoaderData(); // Fully typed as Post!
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Type-Safe Search Params
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
const searchSchema = z.object({
page: z.number().default(1),
sort: z.enum(['newest', 'oldest', 'popular']).default('newest'),
query: z.string().optional(),
});
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
component: PostsList,
});
function PostsList() {
const { page, sort, query } = Route.useSearch(); // Fully typed!
const navigate = Route.useNavigate();
return (
<div>
<select
value={sort}
onChange={(e) => navigate({ search: { sort: e.target.value as any } })}
>
<option value="newest">Newest</option>
<option value="oldest">Oldest</option>
<option value="popular">Popular</option>
</select>
</div>
);
}
Type-Safe Links
// TypeScript ERRORS if the route doesn't exist
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
// TypeScript ERRORS if search params are wrong
<Link to="/posts" search={{ page: 1, sort: 'newest' }}>Posts</Link>
// This would be a compile error:
<Link to="/nonexistent">Bad Link</Link> // Type error!
<Link to="/posts" search={{ page: 'not a number' }}>Bad Search</Link> // Type error!
Pending UI (Loading States)
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => fetchPost(params.postId),
pendingComponent: () => <div>Loading post...</div>,
errorComponent: ({ error }) => <div>Error: {error.message}</div>,
component: PostPage,
});
TanStack Router vs React Router vs Next.js
| Feature | TanStack Router | React Router | Next.js |
|---|---|---|---|
| Type-Safe Routes | Full | No | Partial |
| Type-Safe Params | Full | No | No |
| Type-Safe Search | Full (Zod) | No | No |
| Loaders | Yes | Yes | Server Components |
| File-Based | Yes | No (plugin) | Yes |
| Devtools | Yes | No | N/A |
| SSR | Yes | Yes | Built-in |
Building data-rich React apps? Check out my Apify actors — type-safe data extraction APIs. For custom solutions, email spinov001@gmail.com.
Top comments (0)