Almost every React app needs routing, and almost every React team picks React Router by default — the same library the ecosystem has used for a decade. But 2026 is not 2016. TanStack Router has brought fully type-safe routing to the mainstream with 15,000+ stars, and Wouter proves that a complete router can fit in 2.2 KB. If you are starting a new project today, the default choice may no longer be the right one. Here is the comparison that will save you a rewrite.

TL;DR / Quick Verdict

  • Pick React Router v7 if you want the safest default, a massive ecosystem of tutorials and middleware, and first-class support for loaders, actions, and SSR in one package.
  • Pick TanStack Router if type safety is a hard requirement for your team — every route path, search parameter, and query key is checked at compile time.
  • Pick Wouter if you are shipping an embeddable widget, an extension popup, or any bundle-size-sensitive app, and your routing needs are simple.

The Contenders

All three are open source, MIT-licensed, and actively maintained as of August 2026. They differ radically in philosophy: React Router is the battle-tested generalist, TanStack Router is the type-safety maximalist, and Wouter is the minimalist that refuses to grow.

LibraryGitHub StarsLast UpdatedBundle Size (gzip)Type-Safe RoutesData LoadersSSR / Framework ModeFile-Based Routing
React Router56,5532026-08~14 KBPartial (via codegen)Yes (loaders/actions)Yes (v7 framework mode)Via Vite plugin
TanStack Router14,9312026-08~15 KBFull (paths, params, search)Yes (loader/search)YesYes (built-in)
Wouter7,8652026-08~2.2 KBBasic (typed params)NoNoNo

React Router v7 (released in late 2024 and now the current major line) unified the old react-router-dom and react-router packages into a single react-router dependency and added optional “framework mode” with loaders, actions, and SSR. TanStack Router, from the same author as TanStack Query and TanStack Table, was built from the ground up around end-to-end type inference. Wouter is a single-file router by Andrew Safin that reimplements the classic useRoute/useLocation hook API with zero dependencies.

Use CaseRecommendedWhy
Large production SPA with a team of 5+ developersTanStack RouterCompile-time route checking eliminates entire classes of broken-link bugs
Legacy codebase migration or the “boring choice”React RouterThe entire ecosystem’s tutorials, examples, and job candidates assume it
Next.js-style full-stack app without Next.jsReact Router v7Framework mode gives you loaders, actions, and SSR on your own Vite setup
Embedded widget, iframe, or browser extensionWouter2.2 KB gzipped; hash-based mode works without server config
Strictly typed search parameters (filters, pagination)TanStack Routersearch is validated and typed per route — no more `string
Prototype or weekend projectWouterOne dependency, one file of docs, zero config

React Router — The Battle-Tested Default

With 56,553 stars (updated August 2026), React Router is the most-used routing library in the React ecosystem by a wide margin. Version 7 modernized the API: you now install a single react-router package, define routes as a data structure, and let RouterProvider drive the tree. Loaders and actions give you framework-grade data handling without committing to a meta-framework.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// React Router v7 — data APIs with loaders
import { createBrowserRouter, RouterProvider, Link } from "react-router";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "users/:userId",
    element: <UserProfile />,
    loader: async ({ params }) => {
      const res = await fetch(`/api/users/${params.userId}`);
      return res.json();
    },
  },
]);

export function App() {
  return <RouterProvider router={router} />;
}

// Typed links: <Link to="/users/42"> works out of the box
export function Home() {
  return <Link to="/users/42">View user 42</Link>;
}

The trade-off is that type safety is not automatic. Route paths are still plain strings — the library can infer params only when you opt into its codegen plugin or hand-write typed route objects. That is fine for most teams, but it is exactly the gap TanStack Router exploits.

1
npm install react-router

TanStack Router — Type Safety as the Default

TanStack Router (14,931 stars, updated August 2026 — one of the most active routers in the ecosystem right now) treats the type system as a first-class citizen. Every Link target, every route param, and every search parameter is inferred from your route tree. Invalid routes fail the build, not production.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// TanStack Router — the route tree drives everything
import { createRootRoute, createRoute, createRouter, RouterProvider } from "@tanstack/react-router";

const rootRoute = createRootRoute();

const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "/",
  component: Home,
});

const userRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "users/$userId",
  component: UserProfile,
  validateSearch: (search: Record<string, unknown>) => ({
    tab: (search.tab as string) ?? "overview",
  }),
});

const routeTree = rootRoute.addChildren([indexRoute, userRoute]);
const router = createRouter({ routeTree });

export function App() {
  return <RouterProvider router={router} />;
}

// Typed links: params AND search are compile-time checked
export function Home() {
  return (
    <Link to="/users/$userId" params={{ userId: "42" }} search={{ tab: "activity" }}>
      User activity
    </Link>
  );
}

File-based routing is built in: name a file users.$userId.tsx inside your routes directory and the route tree generates itself, with the same type inference. For teams burned by broken deep links or untyped query strings, this is the strongest argument in the category. The cost: a steeper learning curve and a router that is more opinionated about how you structure your app.

1
npm install @tanstack/react-router

Wouter — The 2.2 KB Minimalist

Wouter (7,865 stars, updated August 2026) proves that a router does not need to be big. The entire library is ~2.2 KB gzipped with zero dependencies, and it exposes a tiny hook-based API that feels like the platform: useLocation for the current path, useRoute for matching, and a <Route> component for declarative rendering. It supports both history and hash modes — the latter being perfect for static hosting and file:// contexts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Wouter — tiny, hook-based, zero dependencies
import { Route, Switch, Link, useRoute } from "wouter";

export function App() {
  return (
    <Switch>
      <Route path="/" component={Home} />
      <Route path="/users/:id" component={UserPage} />
      <Route>404  Nothing here</Route>
    </Switch>
  );
}

function UserPage() {
  const [match, params] = useRoute("/users/:id");
  return <h1>User {params?.id}</h1>;
}

export function Home() {
  return <Link href="/users/42">Go to user 42</Link>;
}

The trade-offs are real: no loaders, no SSR story, and no codegen — params are typed but route matching is runtime string matching. For an embeddable widget or a docs site, that is a feature, not a bug. You can also swap in wouter’s memory and static location adapters for tests and prerendering.

1
npm install wouter

Bundle Size and Performance Reality Check

Bundle size matters differently depending on where the router runs:

  • Wouter at ~2.2 KB gzipped is invisible in any bundle — it will never be the reason your page is slow.
  • React Router and TanStack Router both land in the 10–20 KB gzipped range for core routing. TanStack Router’s codegen adds a few KB but buys compile-time checking.
  • Runtime performance is a non-issue in all three for realistic apps: routing is a few string comparisons and a render. The real performance wins come from code splitting (lazy routes), which all three support. React Router and TanStack Router both offer lazy route definitions; with Wouter you use React.lazy yourself.

The honest conclusion: if you are choosing on bundle size alone, only Wouter’s size is meaningfully different. Everything else is DX and safety.

Common Pitfalls and Migration Traps

  1. Treating “type-safe” as binary. React Router’s codegen gives partial typing; TanStack Router’s is total. If your team expects full inference, verify the actual DX before committing — a “partial” experience can be more confusing than none.
  2. Search params as strings. With React Router, useSearchParams() returns string values, so ?page=2 needs manual Number() parsing and defaulting. TanStack Router’s validateSearch does this once, centrally, with types — worth stealing the pattern even if you stay on React Router.
  3. Hash routing is a trap for deep links. Wouter’s hash mode is great for widgets but breaks server-side link previews and analytics referrers. Use history mode on real websites.
  4. Mixed history state with multiple routers. Mounting two router instances (e.g., an embedded React app inside a host app) causes history fights. Wouter’s memory location is the clean escape hatch; React Router and TanStack Router both support memory history for sub-apps too.
  5. Upgrading from React Router v6. The v7 migration is mostly mechanical (react-router-domreact-router), but useRoutes and Routes-style declarative APIs still exist while the data APIs are the recommended path. Don’t mix both styles in one codebase.
  6. TanStack Router’s opinionated tree. The generated route tree becomes part of your repo; codegen caches and stale trees cause confusing errors. Commit the generated files and regenerate on every route change in CI.
  7. SSR watermarks. All three render on the server, but hydration mismatches around useLocation (e.g., host header differences) are the classic bug. Normalize the URL before rendering on the server.

For more frontend ecosystem comparisons, see our JavaScript state management roundup, the TanStack Query vs SWR vs RTK Query data-fetching comparison, and the JavaScript data grid shootout.

FAQ

Is TanStack Router a drop-in replacement for React Router? No. The APIs are intentionally different (route trees vs. route objects, typed Link props vs. string to). You can migrate incrementally route by route, but you will rewrite link components and navigation calls along the way.

Does React Router v7 still work with React 18? Yes. React Router v7 supports React 18 and 19, and it works with plain Vite setups, Remix-compatible deployments, and custom servers via the react-router package’s framework mode.

Can Wouter handle nested routes? Wouter matches flat paths; for nesting you compose useRoute matches manually or use Switch ordering. If you need deeply nested layouts with outlet rendering, React Router or TanStack Router is the better fit.

Which router has the best developer tooling? TanStack Router ships a dedicated devtools panel (route tree inspection, cache state) similar to TanStack Query’s. React Router relies on browser devtools and its error boundary outputs. Wouter intentionally ships none.

Is file-based routing worth switching for? If your team already uses file-based conventions (like Next.js), TanStack Router’s built-in file routing feels natural and removes route-config drift. If you prefer explicit config, React Router’s data objects are clearer to review.

Do these routers work with React Native? React Router has a dedicated react-router-native package. TanStack Router and Wouter are web-first; community forks exist but are not official.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com