Skip to main content

TanStack Router vs React Router v7: Type-Safe Routing in 2026

·PkgPulse Team

TL;DR

TanStack Router wins on type safety — search params, path params, and loader data are all fully typed with zero configuration. React Router v7 (which merged with Remix) wins on ecosystem size, Server Components support, and the broader Next.js/Remix world. For a standalone React SPA, TanStack Router's type safety is genuinely superior. For a full-stack app with server rendering, React Router v7's loader pattern and Next.js's file-based routing are better integrated.

Key Takeaways

  • TanStack Router: 1.2M weekly downloads (↑ fast), type-safe search params/loaders, devtools, ~40KB
  • React Router v7: 12M+ weekly downloads, merged with Remix, now supports RSC/server mode
  • Type safety: TanStack Router is superior — no casting, no params as { id: string }
  • File-based routing: both support it — TanStack via @tanstack/router-plugin, React Router via conventions
  • Data loading: TanStack's loaderData is typed; React Router v7's loader requires manual typing
  • For SPAs: TanStack Router is the modern choice
  • For SSR/full-stack: React Router v7 + Remix or Next.js

Download Comparison

PackageWeekly DownloadsTrend
react-router~12M→ Stable (Remix merger)
@tanstack/react-router~1.2M↑ +120% YoY
@remix-run/react~2.8M→ (merging into react-router)

TanStack Router is growing extremely fast from a smaller base.


TanStack Router: Full Type Safety

File-Based Routing Setup

// npm install @tanstack/react-router @tanstack/router-plugin

// routes/__root.tsx — root route
import { createRootRoute, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/router-devtools';

export const Route = createRootRoute({
  component: () => (
    <>
      <nav>...</nav>
      <Outlet />
      <TanStackRouterDevtools />
    </>
  ),
});
// routes/posts/$postId.tsx — dynamic route
import { createFileRoute } from '@tanstack/react-router';
import { fetchPost } from '../api';

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    // params.postId is string — fully typed from filename
    return fetchPost(params.postId);
  },
  component: PostPage,
});

function PostPage() {
  // loaderData is typed to return type of fetchPost:
  const post = Route.useLoaderData();  // Post — no casting!
  const { postId } = Route.useParams(); // string — no casting!
  
  return <h1>{post.title}</h1>;
}

Type-Safe Search Params (TanStack's Killer Feature)

// routes/products/index.tsx
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';

const searchSchema = z.object({
  page: z.number().catch(1),
  category: z.enum(['all', 'electronics', 'clothing']).catch('all'),
  q: z.string().optional(),
  sort: z.enum(['price', 'name', 'date']).optional(),
});

export const Route = createFileRoute('/products/')({
  validateSearch: searchSchema,
  component: ProductsPage,
});

function ProductsPage() {
  // All search params are typed — no URL parsing, no casting:
  const { page, category, q, sort } = Route.useSearch();
  //      ^^^^   ^^^^^^^^   ^   ^^^^
  //      number  'all'|'electronics'|'clothing'  string|undefined  etc.
  
  const navigate = useNavigate({ from: Route.fullPath });
  
  return (
    <div>
      <input
        value={q ?? ''}
        onChange={(e) => navigate({ search: (prev) => ({ ...prev, q: e.target.value, page: 1 }) })}
      />
      {/* Pagination: */}
      <button onClick={() => navigate({ search: (prev) => ({ ...prev, page: prev.page - 1 }) })}>
        Previous
      </button>
    </div>
  );
}

Compare this to React Router — you'd use useSearchParams() and manually parse/cast everything.


React Router v7: The Remix Merger

React Router v7 unifies Remix's loader/action patterns with client-side routing:

Full-Stack Mode (Framework Mode)

// app/routes/products.$productId.tsx — like Remix
import type { Route } from './+types/products.$productId';
import { db } from '~/lib/db';

// Typed loader (requires codegen from Remix/RR7 compiler):
export async function loader({ params }: Route.LoaderArgs) {
  const product = await db.product.findUnique({ where: { id: params.productId } });
  if (!product) throw new Response('Not Found', { status: 404 });
  return { product };
}

export default function ProductPage({ loaderData }: Route.ComponentProps) {
  const { product } = loaderData;  // Typed via codegen
  return <h1>{product.name}</h1>;
}
// Server Actions (forms → mutations):
export async function action({ request, params }: Route.ActionArgs) {
  const formData = await request.formData();
  const name = formData.get('name') as string;

  await db.product.update({ where: { id: params.productId }, data: { name } });
  return redirect('/products');
}

Client-Only Mode (SPA)

// React Router v7 in SPA mode (no SSR):
import { createBrowserRouter, RouterProvider } from 'react-router';

const router = createBrowserRouter([
  {
    path: '/products/:productId',
    loader: async ({ params }) => {
      return fetch(`/api/products/${params.productId}`).then(r => r.json());
    },
    Component: ProductPage,
  },
]);

function ProductPage() {
  const data = useLoaderData() as Product;  // Manual cast required
  const { productId } = useParams();         // string | undefined
  
  return <h1>{data.name}</h1>;
}

Side-by-Side

TanStack RouterReact Router v7
Downloads1.2M/week12M+/week
Type safetyEnd-to-end automaticCodegen or manual
Search params typing✅ Zod schemas❌ Manual
SSR/Server ComponentsPartial✅ (Framework mode)
File-based routing✅ Plugin✅ Convention
Devtools
Bundle size~40KB~32KB
EcosystemGrowingVast

Recommendation

Choose TanStack Router if:
  → Building a React SPA (client-side only)
  → Type safety for search params is important
  → Complex query parameters with validation
  → Don't need SSR or Server Components

Choose React Router v7 if:
  → Full-stack app with server rendering
  → Using Remix architecture
  → Need vast ecosystem compatibility
  → Team is already familiar with React Router

Compare TanStack Router and React Router download trends on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.