Back

TanStack Router is the Best Router of All Web Frameworks

There are tons of web frameworks out there. For React alone, we have React Router, Next.js, Remix, TanStack Router, and more. Now, most of them are supporting full-stack features like SSR. Outside of the React world, we have Nuxt, Svelte, Solid, and others. But still, TanStack Router is the best router of all web frameworks!

Let me explain

The Problem with Managing Complex State in URLs

In most web apps, most workloads are CRUD operations to handle different data flows from the server. Take a todo list app as an example: we have a page for the todo items table and a page for individual todo item details. We need to fetch data from the server for both of them.

/todos
/todos/:id

But for the list page, what happens when we want to add more features like filtering, sorting, and pagination? How would you store those states? You can either put them into component state or use them as parameters in the URL.

Usually, putting them in the URL is more user-friendly since users can share URLs with others and bookmark them. However, from a code perspective, URLs are just strings, making it inherently harder to manage complex states while maintaining type safety. Of course, with Next.js we have nuqs. But for TanStack Router, we have a better, more type-safe, native solution.

TanStack Router's useSearch: A Type-Safe Solution

TanStack Router treats search params as structured JSON data rather than simple string key-value pairs. It automatically converts URL search strings to typed objects and back, providing type safety through validation schemas.

How It Works

Here's what makes it powerful:

// This navigation...
<Link
  to="/shop"
  search={{
    pageIndex: 3,
    includeCategories: ["electronics", "gifts"],
    sortBy: "price",
    desc: true,
  }}
>
  Shop
</Link>

// Creates this URL:
// /shop?pageIndex=3&includeCategories=%5B%22electronics%22%2C%22gifts%22%5D&sortBy=price&desc=true

// Which parses back to:
{
  "pageIndex": 3,
  "includeCategories": ["electronics", "gifts"],
  "sortBy": "price",
  "desc": true
}

Notice how arrays and complex data structures are automatically serialized and deserialized? That's the magic!

Type-Safe Search Params with Validation

The real power comes from validation schemas. Here's an example using Zod:

// routes/shop.products.tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'

const productSearchSchema = z.object({
  page: z.number().default(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})

export const Route = createFileRoute('/shop/products')({
  validateSearch: zodValidator(productSearchSchema),
  component: ProductList,
})

function ProductList() {
  const { page, filter, sort } = Route.useSearch()

  return (
    <div>
      <p>Page: {page}</p>
      <p>Filter: {filter}</p>
      <p>Sort: {sort}</p>
    </div>
  )
}

With Route.useSearch(), you get fully typed search params with autocomplete! No more string parsing or type assertions needed.

Updating Search Params

Updating search params is equally elegant. You can use the Link component with a function to update specific params:

function ProductList() {
  return (
    <div>
      {/* Update specific params */}
      <Link
        from="/shop/products"
        search={(prev) => ({ ...prev, page: prev.page + 1 })}
      >
        Next Page
      </Link>

      {/* Set new search params */}
      <Link
        to="/shop/products"
        search={{ page: 1, filter: 'electronics', sort: 'price' }}
      >
        Electronics
      </Link>
    </div>
  )
}

Or programmatically with useNavigate:

import { useNavigate } from '@tanstack/react-router'

function ProductList() {
  const navigate = useNavigate({ from: '/shop/products' })

  const handleNextPage = () => {
    navigate({
      search: (prev) => ({ ...prev, page: prev.page + 1 }),
    })
  }

  return <button onClick={handleNextPage}>Next Page</button>
}

Conclusion

Beyond search params, Tanstack Router offers other powerful features as well such as the integration with Tanstack Query, and more. Still, the seamless handling of search params is what impresses me most. Whenever I explore a new framework, I always check if it provides features similar to Tanstack Router.

If you haven’t tried Tanstack Router yet, I highly recommend giving it a go — you’re truly missing out! I’ve also compiled some notes from my own learning journey, which you can find here. Feel free to check them out if you’d like to explore further!