React Native → Expo Router if you're on Expo, else React Navigation. Web → use the meta-framework's router (see RB-E-META-FRAMEWORKS), or TanStack Router for a type-safe SPA.
Expo app → Expo Router (file-based)
bare RN → React Navigation
type-safe web SPA without a meta-framework → TanStack Router
web SPA already on React Router, or ecosystem/familiarity over route type-safety → React Router 8 (released, ESM-only; its typegen is framework-mode-only — which is why TanStack keeps the type-safe-SPA slot)
adopting React Navigation 8 native tabs → still ALPHA in 2026; pin + verify against the RN8 release notes first
Options & tradeoffs
the field considered — and why each one isn’t the default here
option
tradeoff
evidence
React Navigation
RN default; mature; imperative + declarative
ships in 13/34
Expo Router
file-based; great if on Expo; convention over config; v56 (SDK 56) DECOUPLED from React Navigation — its own native-first core; direct @react-navigation/* imports no longer work alongside it
3.5M/wk · ships in 1/34
React Router (8.x)
web routing; absorbed Remix; v8 (8.2.0, 2026-07) RELEASED — ESM-only (drops CJS + react-router-dom), future flags on by default; v7→v8 generally non-breaking if you're caught up on v7 future flags
45.3M/wk · ships in 9/34
TanStack Router / Start
type-safe web routing; signal-based core; Start is its RSC/SSR meta-framework (see RB-E-META-FRAMEWORKS)
18.9M/wk · ships in 2/34
Next.js App Router
web, full-framework routing + RSC (see RB-E-META-FRAMEWORKS)
v8 (8.x, released 2026) is ESM-only and drops both CJS and the react-router-dom package; v7→v8 is generally non-breaking if you're caught up on v7 future flags
npm weekly downloads (from the corpus's last signals run): expo-router 3.5M · react-router-dom 45.3M · react-router 42.6M · @tanstack/react-router 18.9M
Verified notes
React Navigation 8.0 (ALPHA, announced 2025-12) makes NATIVE bottom tabs the default (via react-native-screens — native look incl. iOS 26 liquid glass), adds native icon components (SFSymbol on iOS / MaterialSymbol on Android — smaller bundles), reworks TypeScript inference, and adds screen-history APIs (pushParams, persistor, inactiveBehavior via React.Activity). Reflects the trend toward honouring platform UI. Still ALPHA in 2026 — pin/verify before adopting. Verified vs the React Navigation blog. RN8 JULY PROGRESS (2026-07-08, verified vs the React Navigation blog): still PRE-STABLE (next tag) — adds Suspense-enabled navigation (interruptible, concurrent), experimental UNSTABLE_loader data loaders (TanStack Query/Apollo prefetch), full parent-navigator type inference (drops CompositeNavigationProp), and a cross-library STANDARD NAVIGATION API so custom navigators work in both React Navigation AND Expo Router — the first bridge across the newly-decoupled stacks. Requires screens 4.25+, gesture-handler 3.0+, TS 6+. UPDATE 2026-07-09: Expo Router v56 (SDK 56) NO LONGER DEPENDS on React Navigation — "expo-router no longer depends on react-navigation"; code importing @react-navigation/* directly breaks when adopting it (verified vs the SDK 56 changelog; a codemod exists — npx expo-codemod sdk-56-expo-router-react-navigation-replace). The decoupling persists in SDK 57 (expo-router 57.x ships zero @react-navigation/* deps, verified vs npm 2026-07-10). The Expo-vs-bare split above now separates two DISTINCT navigation stacks, not one wrapper over the other — which raises the cost of switching lanes later; weigh it at project setup. Stress-tested 2026-07-10 (WEAKENED → React Router when-clause added; the TanStack type-safe-SPA slot SURVIVED the 2.3x-downloads attack — RR's typegen is framework-mode-only).
Canonical reading
Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.
Production evidence for React Router 7 framework mode at billion-user scale: ChatGPT migrated Next.js Pages Router → Remix (2024) → RR7 with streaming SSR, TanStack Query seeded from server render, Tailwind 4 tokens, ProseMirror composer — 84KB HTML document, 50-65ms TTFB. The concrete counterweight to 'serious apps need Next.js', and the highest-profile data point behind this entry's React Router when-clause. (4 independent newsletter signals in one week.)
The primary source behind this entry's 'signal-based core' claim: the monolithic router.state was decomposed into a graph of independent stores so route changes update only affected subscribers (React navigation 7ms→4.5ms in their measurements). The architecture under TanStack Router's fine-grained reactivity.
Instrumented proof that Expo Router defers screen loading by construction via require.context() getters wrapped in getComponent thunks — with the precise caveats (layouts/initial route load eagerly; sync require, not bundle-splitting). The canonical explanation of Expo Router's lazy-loading mechanism.
Frames loaders/actions as the HTTP-to-domain seam — keep business logic in testable services, reserve loaders/actions for the integration layer (E2E). A durable testing/architecture philosophy for React Router.
The redirect-after-login problem: a deep link that lands on the login screen should still deliver the user to the intended screen afterwards. Walks the manual approaches in older React Navigation and the new API that handles it (routeNamesChangeBehavior 'lastUnhandled' re-dispatching the unhandled link when auth state flips). From the library's own maintainer.
Models modal dialogs as nested routes in React Router 7 instead of local useState/useEffect — covering revalidation, view transitions, flash sessions, and exit animations. A durable routing pattern that generalizes beyond the example.
The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.
About navigation & routing in React & React Native
Diataxis: Explanation. This page builds understanding of the navigation/routing
decision — the reasoning behind the pick. It is not a tutorial: the candidate list and
one-line tradeoffs live in the index entry RB-E-NAV; framework-level routing (SSR/RSC)
is owned by RB-E-META-FRAMEWORKS. Read this to understand why.
The one distinction that organises everything
"Navigation" hides two different problems that share a word but not a nature:
Native navigation (React Native) is a UI concern. The thing you choose is how
screens stack, how tabs and drawers behave, and how the platform's own transitions and
gestures (the iOS back-swipe, native bottom tabs, the Android predictive back) feel.
There is no URL; there is a navigation state tree.
Web routing (React DOM) is a URL concern, and increasingly a data/server concern.
The route is the address bar, history, deep links, and — once a meta-framework is
involved — where data loads and where the server boundary sits.
So the first question is never "which router?" It is "which world am I in, and on the web,
do I already have a meta-framework?" Everything else follows.
The default, and why
React Native: Expo Router if you're on Expo, otherwise React Navigation.
Web: use the meta-framework's router (RB-E-META-FRAMEWORKS); for a type-safe SPA
with no meta-framework, TanStack Router.
The RN default is about fit, not superiority. React Navigation is the mature, bare-RN
baseline — imperative and declarative, deeply customizable. Expo Router layers
file-based routing on top of it: if your project is already Expo, convention-over-config
removes a class of boilerplate and gives you deep-linking and universal (web) routing close
to free. The choice is "are you on Expo?", not "which is better."
On the web, routing rarely stands alone anymore. If you've chosen Next.js or TanStack Start,
they own routing — picking a separate router would fight the framework. The standalone
choice (TanStack Router) earns its place precisely when you want a client-rendered SPA
with first-class type-safe routes and don't want a server framework.
The landscape, and when each one wins
React Navigation — the bare-RN default. Wins on maturity and control. The 2026 trend it
embodies is honouring the platform: React Navigation 8.0 (still alpha through 2026)
makes native bottom tabs the default and adds native icon components (SFSymbol on iOS,
Material Symbols on Android). That direction matters more than the version — but because 8.0
is alpha, pin and verify before adopting its native tabs.
Expo Router — file-based routing for Expo apps, and the seam through which an Expo app
also gets web routing. It's the right default the moment you're on Expo; its cost is Expo
coupling. (It is also the RN/universal meta-framework — see RB-E-META-FRAMEWORKS.)
React Router (8.x) — the web workhorse that absorbed Remix. v8 is released, ESM-only
(dropped CommonJS and the react-router-dom split), with future flags on by default; v7→v8
is largely non-breaking if you kept up with v7's flags. Use it for web routing when you want
a router (or framework mode) without committing to Next.
TanStack Router / Start — type-safe routing with a signal-based core. Router is the
SPA story (the strongest type-safety in the field); Start is its SSR/RSC meta-framework
(RB-E-META-FRAMEWORKS). Reach for Router when end-to-end route type-safety is the thing you
care about.
Next.js App Router — not a router you "add" but the routing of a full framework
(RB-E-META-FRAMEWORKS); listed here only so the boundary is clear.
Tradeoffs and failure modes to name out loud
Adopting alpha as if it were stable. React Navigation 8's native tabs are compelling
but alpha in 2026; shipping on them without pinning is a self-inflicted upgrade treadmill.
Fighting your meta-framework's router. Bolting a second router onto Next/Start is the
classic web mistake — the framework already owns the URL, data, and server boundary.
Flattening platform navigation. A single shared navigation abstraction across web and
native tends to produce non-native transitions and gesture handling. Navigation is the
layer where "share everything" hurts most (see RB-E-CROSSPLATFORM); share the screens'
logic, not necessarily the navigator.
Treating routing as pure UI on the web. On the web the URL is also application state
and a data trigger; modelling it as local component state (modals that should be routes,
filters that should be search params) is a recurring smell.
How it interacts with the rest of the stack
Meta-frameworks (RB-E-META-FRAMEWORKS). On the web this is the dominant fork:
framework-owned routing vs a standalone SPA router. This page is "which navigator"; that
page is "do you have a server framework at all."
Cross-platform (RB-E-CROSSPLATFORM). Solito and Expo Router shape how routing is
shared web↔native; the honest default is to share screens and logic, not force one
navigator across both worlds.
State (RB-E-STATE). The URL is server-adjacent state on the web (search params,
route params); keep it in the router, not duplicated in a client store.
In one paragraph
Decide the world before the router. On React Native, navigation is native UI:
Expo Router if you're on Expo, else React Navigation — and honour platform tabs/gestures
rather than flattening them (React Navigation 8's native tabs are the trend, but pin them
while they're alpha). On the web, routing belongs to your meta-framework if you have one;
if you don't and you want a type-safe SPA, reach for TanStack Router, or React Router 8 for a
framework-agnostic router. The recurring mistake is treating these two worlds as one.