DEV Community

Alex Spinov
Alex Spinov

Posted on

TanStack Router Has a Free Type-Safe Router That Puts React Router to Shame

What if your router caught bugs at compile time instead of production? TanStack Router is the first React router with 100% type-safe navigation, search params, and route context.

The Type Safety Problem

Every React router has the same weakness:

// React Router — typo? Runtime error. Wrong params? Runtime error.
<Link to="/usrs/123" /> // Typo — no error until production
const { id } = useParams(); // id is string | undefined — no guarantees
Enter fullscreen mode Exit fullscreen mode

TanStack Router fixes this:

// TanStack Router — compiler catches everything
<Link to="/usrs/$id" /> // ❌ TypeScript error: route doesn't exist
<Link to="/users/$id" params={{ id: "123" }} /> //  Fully typed
Enter fullscreen mode Exit fullscreen mode

Route Definitions

// routes/users.$userId.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/users/$userId")({
  // Validate & parse search params
  validateSearch: (search) => ({
    page: Number(search.page) || 1,
    sort: (search.sort as "name" | "date") || "name",
  }),
  // Load data before rendering
  loader: async ({ params }) => {
    // params.userId is typed as string — guaranteed
    return fetchUser(params.userId);
  },
  component: UserPage,
});

function UserPage() {
  const { userId } = Route.useParams(); // Typed!
  const { page, sort } = Route.useSearch(); // Typed!
  const user = Route.useLoaderData(); // Typed!
  return <div>{user.name}  Page {page}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Type-Safe Search Params

This is TanStack Router's killer feature:

// Define search params schema per route
validateSearch: z.object({
  page: z.number().default(1),
  filter: z.enum(["all", "active", "done"]).default("all"),
  search: z.string().optional(),
}),

// Now search params are fully typed everywhere
const { page, filter, search } = Route.useSearch();
// page: number, filter: "all" | "active" | "done", search: string | undefined

// Navigate with type-safe search params
navigate({ search: { page: 2, filter: "active" } }); // ✅
navigate({ search: { page: "two" } }); // ❌ TypeScript error
Enter fullscreen mode Exit fullscreen mode

Built-in Data Loading

const route = createFileRoute("/dashboard")({
  // Runs before component renders
  loader: async () => {
    const [stats, notifications] = await Promise.all([
      fetchStats(),
      fetchNotifications(),
    ]);
    return { stats, notifications };
  },
  // Pending UI while loading
  pendingComponent: () => <Spinner />,
  // Error UI
  errorComponent: ({ error }) => <ErrorPage error={error} />,
});
Enter fullscreen mode Exit fullscreen mode

vs React Router v7

Feature React Router v7 TanStack Router
Type-safe params Partial (generated) 100% (inferred)
Type-safe search params No Yes (with validation)
Type-safe navigation No Yes (compile-time)
Bundle size ~15KB ~12KB
SSR support Yes Yes (TanStack Start)
Devtools No Yes (built-in)
Maturity Battle-tested Newer but stable

Getting Started

npm create @tanstack/router@latest
Enter fullscreen mode Exit fullscreen mode

Or add to existing project:

npm install @tanstack/react-router @tanstack/router-plugin
Enter fullscreen mode Exit fullscreen mode

Building complex web applications? I specialize in data-intensive tools and web scraping solutions. Reach out at spinov001@gmail.com or explore my Apify tools.

Top comments (0)