react-brain
Browse decisions
React & language foundations 3App architecture 9UI 12Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness bencharchitecturechangelogmethodologyroadmap

entriesapp-architecture · verified 2026-07-13 · react + react-native

Navigation & routing

reviewedconfidence: mediumtrack: weakenedthis tier holds 50% on the public scorecard (3/19 graded · 0 overturned) →

related decisions: meta-frameworks

cited by: meta-frameworks

re-verified 4× — 2026-07-13 · 2026-07-10 · 2026-07-09 · 2026-07-06 · changelog

recommendation

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

optiontradeoffevidence
React NavigationRN default; mature; imperative + declarativeships in 13/34
Expo Routerfile-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 it3.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 flags45.3M/wk · ships in 9/34
TanStack Router / Starttype-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 Routerweb, full-framework routing + RSC (see RB-E-META-FRAMEWORKS)

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

Migration lines

version and deprecation lines this decision tracks — for YOUR repo's sequenced plan: the doctor or npx -y @heart-it/react-brain migrate .

react-router-dom → react-router v8 (single package — v8 drops react-router-dom) [superseded · effort M]

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

reactrouter.com

react-router < 8.0.0 → react-router v8 [upgrade · effort S]

ESM-only, future flags on by default; generally non-breaking from a flags-current v7

reactrouter.com

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.

Reverse Engineering ChatGPT Web: How OpenAI Built for a Billion UsersDennis Brotzky (performance.dev)

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.)

TanStack Router's New Reactive Core: A Signal GraphFlorian Pellet (TanStack)

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.

Exploring Inlined Requires: does Expo Router give you screen-level lazy loading?Andrei Calazans

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.

React Router loaders and actions as integration pointsSergio Xalambrí (React Router / Remix)

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.

Deep Links With Authentication in React NavigationSatyajit Sahoo (Callstack; React Navigation maintainer)

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.

Untangling Dialogs in React RouterDave Adams

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.

Sources

The full explanation

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.


See also: RB-E-META-FRAMEWORKS (server-owned routing + SSR/RSC), RB-E-CROSSPLATFORM (sharing navigation web↔native), RB-E-STATE (the URL as state).

Related in app-architecture: state · data · p2p · meta-frameworks · forms · networking · crossplatform · desktop