React Router works. But if you've ever had a broken link in production because of a typo in a route string, you know the pain. TanStack Router catches those errors at compile time.
What Is TanStack Router?
TanStack Router is a fully type-safe router for React. Every route, every parameter, every search param — validated at compile time. If it compiles, it routes correctly.
Quick Start
npm install @tanstack/react-router
// Define routes with full type safety
import { createRootRoute, createRoute, createRouter } from '@tanstack/react-router';
const rootRoute = createRootRoute({
component: RootLayout,
});
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: HomePage,
});
const userRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users/$userId',
component: UserPage,
// Validate params at the type level
parseParams: (params) => ({
userId: Number(params.userId),
}),
});
const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, userRoute]),
});
Type-Safe Links
// This COMPILES — route exists, params correct
<Link to="/users/$userId" params={{ userId: 123 }}>
View User
</Link>
// This FAILS at compile time — route doesn't exist
<Link to="/uusers/$userId" params={{ userId: 123 }}>
View User
</Link>
// TS Error: Type '"/uusers/$userId"' is not assignable
// This FAILS — missing required param
<Link to="/users/$userId">
View User
</Link>
// TS Error: Property 'userId' is missing
Search Params (Type-Safe)
const productsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/products',
validateSearch: (search) => ({
page: Number(search.page) || 1,
category: search.category as string || 'all',
sort: (search.sort as 'price' | 'name') || 'name',
}),
component: ProductsPage,
});
function ProductsPage() {
// search is fully typed: { page: number, category: string, sort: 'price' | 'name' }
const { page, category, sort } = productsRoute.useSearch();
return <ProductList page={page} category={category} sort={sort} />;
}
Data Loading
const userRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users/$userId',
loader: async ({ params }) => {
// params.userId is typed as number
return fetchUser(params.userId);
},
component: UserPage,
});
function UserPage() {
const user = userRoute.useLoaderData(); // Fully typed
return <h1>{user.name}</h1>;
}
Why TanStack Router Over React Router
| Feature | TanStack Router | React Router |
|---|---|---|
| Type-safe links | Compile-time | Runtime strings |
| Search params | Validated + typed | Manual parsing |
| Route params | Typed + parsed | String only |
| Devtools | Built-in | Community |
| Data loading | Integrated | Loader pattern |
| Preloading | Hover intent | Manual |
Key Features
- 100% type-safe — routes, params, search params, loader data
- Built-in devtools — inspect route tree, params, cache
- Preloading — prefetch data on hover
- Pending states — loading indicators per route
- Error boundaries — per-route error handling
- SSR support — server-side rendering compatible
Get Started
- Documentation
- GitHub — 9K+ stars
- Examples
Building a data-driven React app? My Apify scrapers provide structured web data via API. Custom solutions: spinov001@gmail.com
Top comments (0)