DEV Community

Alex Spinov
Alex Spinov

Posted on

TanStack Router Has Free Type-Safe Routing — Here's Why It's the Future of React Routing

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  ),
});
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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,
});
Enter fullscreen mode Exit fullscreen mode

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)