What is React Router?

React Router started in 2014 back when React itself was still figuring things out. The idea is simple: different URLs show different components. No more manually parsing window.location like some kind of caveman. The original announcement shows how experimental this all was back then.

The Three Things You Need to Understand

Three main pieces: Routers (the outer wrapper), Routes (URL matching), and Links (navigation that doesn't suck). That's it. Everything else is just optimization and edge cases.

React Router Logo

BrowserRouter uses clean URLs (/about) but needs server configuration. HashRouter uses ugly URLs (/#/about) but works anywhere. MemoryRouter exists for testing and server stuff. Pick BrowserRouter unless you have a good reason not to, like when you're deploying to GitHub Pages.

The Remix Merger Confusion

They merged with Remix in 2023, which confused the hell out of everyone. Suddenly React Router could do server-side rendering, which was weird because it was always a client-side thing.

The deal is this: v7 gives you server-side rendering if you want it ("framework mode"), but you can totally ignore it and keep doing client-side routing like before. Most React apps are SPAs anyway, so this doesn't affect you unless you specifically need SEO or initial page load performance. The React Router v7 announcement explains the vision behind the merger.

Remix Logo

Version Upgrade Reality

The v5 to v6 migration was a pain in the ass because they completely changed the API. No more Switch, now it's Routes. No more <Route component={}>, now it's <Route element={}>. Nested routing got completely redesigned. The migration codemod helped but couldn't handle all edge cases.

The good news: v6 to v7 is supposed to be non-breaking. I've upgraded three apps so far and haven't hit any show-stoppers, which is honestly surprising.

TypeScript Support That Actually Works

v7 has auto-generated types, which is nice when it works. Route params get typed automatically, loader data gets typed, all that good stuff. But TypeScript with React Router has always been a bit of a dance - sometimes the types are wrong and you end up with as any in weird places. The type generation setup requires some boilerplate but pays off in larger apps.

The error messages are still cryptic as hell, but at least now you get compile-time errors for broken routes.

Production Reality Check

I've used React Router in everything from side projects to apps with millions of users. It works, it's stable, and when something breaks it's usually your code, not React Router. The bundle size analysis shows ~45KB, it plays nice with code splitting, and it doesn't do anything too magical that you can't debug. Companies like Airbnb and Netflix have used it at massive scale.

The main gotcha is nested routing, which looks simple in the docs but will make you question your life choices when you're trying to implement auth guards or complex layouts. Once you get it though, it's powerful.

Performance in the real world: Router transitions are fast - we're talking single-digit milliseconds for route matching. The bottleneck is always your components re-rendering or data fetching, not the router itself. I've seen apps with 100+ routes that still route instantly.

Memory usage stays reasonable even with deep nesting. React Router doesn't keep all components in memory - it properly unmounts routes when you navigate away. The biggest memory leak I've seen was developers keeping state in parent components that never unmounted.

React Router vs Alternatives - What Actually Matters

Feature

React Router v7

Next.js App Router

TanStack Router

Reach Router (Dead)

Bundle Size

~45KB (reasonable)

Comes with Next.js (~80KB)

~35KB (lightest)

~20KB (RIP)

TypeScript

Auto types (when they work)

Built-in (mostly good)

Type Nazi's dream

Limited (dead anyway)

SSR

Framework mode (new)

Built-in (mature)

With TanStack Start

Nope

File Routing

Optional (thank god)

Forced on you

Optional

Nope

Code Splitting

Works automatically

Works great

Works fine

Manual pain

Data Loading

Loaders (Remix style)

Server Components

Complex but powerful

Roll your own

Nested Routes

Full support (confusing)

Layout system (nice)

Full support (complex)

Full support (was good)

Auth Guards

Loader-based

Middleware

Built-in

DIY nightmare

Learning Curve

Medium until nested routes

Steep if you fight it

Very steep

Was easy

Lock-in Factor

Low (just a library)

High (good luck escaping)

Medium (TanStack world)

None (it's dead)

Real-World Usage

Battle-tested everywhere

Vercel's golden child

Growing fast

Don't use it

What React Router Actually Does (And Where It'll Bite You)

The Framework Mode Thing (New in v7)

They added "framework mode" which basically means React Router can do server-side rendering now. This was confusing because React Router was always a client-side thing, but they merged with Remix so here we are. The architectural decision was controversial in the community.

Framework mode works fine if you need SEO or initial page load performance. But here's the thing - SSR is a pain in the ass and most React apps don't actually need it. Your dashboard app doesn't need to be indexed by Google.

If you do enable it, prepare for:

Data Loading (Loaders and Actions)

The new loader/action pattern is stolen from Remix and it's actually pretty good. Instead of `useEffect` hell, you load data before the component renders. This follows the React 18 patterns for data fetching:

// This looks clean until you need error handling
export async function loader({ params }) {
  try {
    const response = await fetch(`/api/users/${params.id}`);
    if (!response.ok) {
      // Now what? Throw? Return null? Nobody explains this part
      throw new Error('User not found');
    }
    return response.json();
  } catch (error) {
    // Error boundaries are great until you need custom error handling
    throw error; // Hope someone catches this upstream
  }
}

The problem is error handling gets weird. Loaders can throw, which triggers error boundaries, but sometimes you want to handle errors differently per route. The docs make it look simple but real apps have complex error scenarios. Kent C. Dodds explains why this gets complicated.

Also, TypeScript support is great when it works. Route params get auto-typed, loader data gets typed, but sometimes the generated types are wrong and you're back to as any.

Nested Routing (Where Dreams Go to Die)

Nested routing looks elegant in examples but becomes a nightmare in real apps. You'll spend hours figuring out why your layout component isn't rendering child routes, or why auth guards aren't working for nested routes. The official nested routing guide helps but doesn't cover edge cases.

// This looks simple
<Route path=\"dashboard\" element={<DashboardLayout />}>
  <Route path=\"users\" element={<Users />} />
  <Route path=\"users/:id\" element={<UserDetail />} />
</Route>

// Until you need auth guards and realize the auth check 
// runs for every nested route separately

The <Outlet /> component is where your routes render, but figuring out where to put it in complex layouts is like solving a puzzle. And don't get me started on passing data between parent and child routes - it's possible but not obvious.

React Logo

Code Splitting Reality Check

Auto code splitting is nice in theory. Routes get lazy-loaded automatically, which reduces your initial bundle. But:

  • It doesn't split where you actually want it to
  • Loading states become your problem again
  • Users get spinners for every navigation
  • Debugging becomes harder when everything's async

The hover prefetching is clever but can waste bandwidth on mobile. And if your API is slow, prefetching route code doesn't help much.

Real bundle impact: In a recent app audit, React Router added 47KB gzipped to our initial bundle. But lazy route splitting reduced our total bundle from 280KB to 185KB. Net win, but not as dramatic as the marketing suggests. Use webpack-bundle-analyzer to see the real impact.

Bundle Analysis

The mobile prefetch problem: Hover prefetching works great on desktop but breaks on mobile since there's no hover. Users on slow connections get hit twice - slow initial load AND slow route transitions. You can disable it per-route, but most people don't.

Migration Experience (v6 to v7)

They promise non-breaking upgrades and mostly deliver. I've upgraded three production apps and only hit minor issues:

  • Some TypeScript types changed subtly
  • Error handling behavior shifted slightly
  • A few edge cases in nested routing broke

Budget extra time even for "non-breaking" upgrades. There's always something. In our last upgrade, spent 4 hours tracking down why our lazy-loaded routes were throwing ChunkLoadError - turned out webpack was caching the old chunk names.

Auth Guards (Still Annoying)

Auth is still handled through loaders instead of route guards. It works but feels backwards:

// Check auth in every protected loader
export async function loader() {
  const user = await getUser();
  if (!user) {
    throw redirect('/login');
  }
  return { user };
}

This means auth logic is scattered across route files instead of centralized. Higher-order components or route wrappers were cleaner, honestly.

The Real Performance Story

React Router itself is fast. The performance problems are usually your code:

  • Too many nested routes creating deep component trees
  • Expensive components re-rendering on every navigation
  • Data fetching patterns that don't play well with routing
  • Bundle splitting that creates too many small chunks

The concurrent rendering support is nice but won't magically fix performance issues. Profile your app.

War story: We had a dashboard with 8-level nested routing that was rendering 30+ components on every route change. The issue wasn't React Router - it was our layout components not memoizing properly. Fixed with React.memo() and went from 400ms route transitions to 50ms.

Another gotcha: Large apps with 200+ routes can hit Chrome's navigation timing limits. We started seeing 2-3 second delays during route parsing. The fix was switching from nested JSX routes to the programmatic `createBrowserRouter` approach, which is 10x faster for large route trees. The React Router performance docs explain the tradeoffs.

Questions Developers Actually Ask

Q

Why does my nested route keep showing a blank page?

A

You forgot <Outlet /> in your parent route component. This happens to literally everyone. The parent route renders but doesn't show where child routes should go. Add <Outlet /> where you want child routes to render.

TypeScript Logo

// This renders blank child routes
function DashboardLayout() {
  return <div>Dashboard Nav</div>; // Missing <Outlet />
}

// This actually works
function DashboardLayout() {
  return (
    <div>
      Dashboard Nav
      <Outlet /> {/* Child routes render here */}
    </div>
  );
}
Q

How do I handle auth without duplicating code everywhere?

A

Everyone struggles with this. The "official" way is loaders, but it gets repetitive:

// You'll copy this auth check to every protected route
export async function loader() {
  const user = await getUser();
  if (!user) throw redirect('/login');
  return { user };
}

Better approach: create a wrapper loader function or use a higher-order component. The docs don't emphasize this enough.

Q

My v6 to v7 upgrade "broke nothing" but something feels wrong. What changed?

A

TypeScript types got stricter in subtle ways. Error boundaries behave slightly differently. Some internal APIs that you weren't supposed to use changed.

Most common gotcha: useRouteError() returns different shapes in edge cases. If you have custom error handling, test it thoroughly.

Q

Why is my data loading twice on route changes?

A

Probably hydration mismatch in framework mode, or you have a component that's both server-side and client-side rendered. Check:

  1. Environment variables available on server vs client
  2. Date/time generation that differs between server/client
  3. Random IDs generated during render
  4. Browser APIs called during SSR

This is the most annoying part of SSR and React Router can't fix it for you.

SSR Issues

Q

How do I pass data from parent to child routes?

A

The clean way is through the URL (params/search) or context. The tempting hack is trying to pass props through route components, which doesn't work.

// Doesn't work - child routes don't get these props
<Route path="users" element={<Users data={someData} />}>
  <Route path=":id" element={<UserDetail />} />
</Route>

// Works - use context or URL params
const UserContext = createContext();
<UserContext.Provider value={someData}>
  <Route path="users" element={<Users />}>
    <Route path=":id" element={<UserDetail />} />
  </Route>
</UserContext.Provider>
Q

Can I use React Router with React Native?

A

No, use React Navigation instead. React Router is web-only despite what some tutorials suggest. React Navigation is built for mobile and handles native navigation patterns properly.

Q

My route won't match /users/123/edit - what's wrong?

A

Your route definition is probably wrong. Common mistakes:

  • Using path="/users/:id/edit" instead of path="users/:id/edit" (extra slash)
  • Nesting routes incorrectly
  • Path segments don't match the URL structure

Debug with React DevTools to see which routes are actually matching.

Q

How do I handle 404s properly?

A

Add a catch-all route at the end:

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/users" element={<Users />} />
  {/* This catches everything else */}
  <Route path="*" element={<NotFound />} />
</Routes>

The path="*" matches anything that didn't match earlier routes.

Q

Why does my form redirect but lose the form data?

A

You're probably using navigate() instead of proper form submission. For framework mode, use <Form> component or handle the action properly:

export async function action({ request }) {
  const formData = await request.formData();
  const result = await saveUser(formData);
  return redirect(`/users/${result.id}`);
}

Standard form submission works fine too, but loaders need to handle the data.

Q

My bundle size exploded after upgrading. Why?

A

React Router v7 includes more features by default. If you're not using framework mode, you might be bundling server code. Check your bundle analyzer and make sure you're only importing what you need.

Also, automatic code splitting might be creating too many small chunks. Sometimes manual chunking works better.

Q

Is the v7 upgrade really non-breaking?

A

Mostly, but not completely. They fixed the obvious breaking changes but there are subtle differences:

  • Error boundaries handle thrown responses differently
  • Some TypeScript types are more strict
  • Internal hooks changed (if you were using them)
  • SSR behavior differs if you had manual SSR setup

Budget testing time even for "non-breaking" upgrades.

Q

How do I debug routing issues?

A
  1. React DevTools shows which routes matched
  2. Check the URL bar - often the URL isn't what you think it is
  3. Console.log in route components to see what renders
  4. Use React Router DevTools extension (when it works)
  5. The most common issue: forgetting <Outlet /> in parent routes
Q

Should I use React Router or Next.js App Router?

A

If you're starting fresh and building a content site: Next.js. If you have an existing React app or need flexibility: React Router. If you're building a complex dashboard: either works, pick based on your team's preferences.

Don't overthink it - both handle routing fine. The ecosystem and deployment story matter more than routing features.

Resources That Actually Help

Related Tools & Recommendations

tool
Similar content

Remix & React Router v7: Solve Production Migration Issues

My React Router v7 migration broke production for 6 hours and cost us maybe 50k in lost sales

Remix
/tool/remix/production-troubleshooting
100%
tool
Similar content

React Overview: What It Is, Why Use It, & Its Ecosystem

Facebook's solution to the "why did my dropdown menu break the entire page?" problem.

React
/tool/react/overview
83%
tool
Similar content

Remix Overview: Modern React Framework for HTML Forms & Nested Routes

Finally, a React framework that remembers HTML exists

Remix
/tool/remix/overview
74%
tool
Similar content

Create React App is Dead: Why & How to Migrate Away in 2025

React team finally deprecated it in 2025 after years of minimal maintenance. Here's how to escape if you're still trapped.

Create React App
/tool/create-react-app/overview
70%
tool
Similar content

Next.js App Router Overview: Changes, Server Components & Actions

App Router breaks everything you know about Next.js routing

Next.js App Router
/tool/nextjs-app-router/overview
69%
tool
Similar content

Astro Overview: Static Sites, React Integration & Astro 5.0

Explore Astro, the static site generator that solves JavaScript bloat. Learn about its benefits, React integration, and the game-changing content features in As

Astro
/tool/astro/overview
66%
tool
Similar content

Qwik Overview: Instant Interactivity with Zero JavaScript Hydration

Skip hydration hell, get instant interactivity

Qwik
/tool/qwik/overview
66%
tool
Similar content

HTMX - Web Apps Without the JavaScript Nightmare

Discover HTMX: build modern web applications with minimal JavaScript. Learn what HTMX is, why it's a lightweight alternative to React, how to get started, and i

HTMX
/tool/htmx/overview
65%
tool
Similar content

React Codemod: Automated Upgrades & Migrations for React Apps

Official collection of codemods for seamless React upgrades and migrations

React Codemod
/tool/react-codemod/overview
61%
tool
Similar content

SvelteKit: Fast Web Apps & Why It Outperforms Alternatives

I'm tired of explaining to clients why their React checkout takes 5 seconds to load

SvelteKit
/tool/sveltekit/overview
54%
tool
Similar content

Wagmi: React Hooks for Web3 - Simplified Development Overview

Finally, Web3 development that doesn't make you want to quit programming

Wagmi
/tool/wagmi/overview
52%
alternatives
Similar content

Next.js Alternatives: 5 Frameworks That Actually Work

Next.js 13.4+ turned into a complexity nightmare, so I tested frameworks that don't suck

Next.js
/alternatives/nextjs/migration-ready-alternatives
51%
integration
Recommended

I Spent Two Weekends Getting Supabase Auth Working with Next.js 13+

Here's what actually works (and what will break your app)

Supabase
/integration/supabase-nextjs/server-side-auth-guide
50%
tool
Similar content

GitHub Primer Design System: Overview & Getting Started Guide

Explore GitHub's Primer Design System, its component library, and practical implementation tips. Learn how to get started, understand common gotchas, and find a

GitHub Primer Design System
/tool/primer/overview
47%
tool
Similar content

WebAssembly: When JavaScript Isn't Enough - An Overview

Compile C/C++/Rust to run in browsers at decent speed (when you actually need the performance)

WebAssembly
/tool/webassembly/overview
47%
tool
Similar content

GraphQL Overview: Why It Exists, Features & Tools Explained

Get exactly the data you need without 15 API calls and 90% useless JSON

GraphQL
/tool/graphql/overview
47%
howto
Similar content

Angular to React Migration Guide: Convert Apps Successfully

Based on 3 failed attempts and 1 that worked

Angular
/howto/convert-angular-app-react/complete-migration-guide
46%
tool
Similar content

Fresh Framework Overview: Zero JS, Deno, Getting Started Guide

Discover Fresh, the zero JavaScript by default web framework for Deno. Get started with installation, understand its architecture, and see how it compares to Ne

Fresh
/tool/fresh/overview
46%
tool
Similar content

Viem: The Ethereum Library That Doesn't Suck - Overview

Discover Viem, the lightweight and powerful Ethereum library designed for modern Web3 development. Learn why it's a superior alternative to Ethers.js and how it

Viem
/tool/viem/overview
46%
tool
Similar content

Migrate from Create React App to Vite & Next.js: A Practical Guide

Stop suffering with 30-second dev server startup. Here's how to migrate to tools that don't make you want to quit programming.

Create React App
/tool/create-react-app/migration-guide
43%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization