Skip to main content

πŸ—ΊοΈ Lesson 6.1: React Router Basics

Think of a website as a building. Traditional sites hand you a fresh, freshly-printed floor plan every time you walk through a door β€” the whole page reloads. React Router turns your app into a smart building where the walls rearrange themselves instantly around you as you move from room to room, no reprinting required. In this lesson you'll master client-side routing with React Router v7, so you can build fast, app-like single-page applications where navigation feels seamless.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what Single Page Applications (SPAs) are, how they differ from multi-page apps (MPAs), and why routing matters
  • Install React Router v7 and wire it into a React 19 + TypeScript project
  • Define routes two ways β€” the declarative <BrowserRouter>/<Routes>/<Route> approach and the modern createBrowserRouter + RouterProvider data router
  • Navigate between pages with <Link> and <NavLink>, and jump programmatically with useNavigate
  • Build nested layouts with <Outlet> and read typed route params with useParams
  • Assemble a complete, type-safe multi-page application with professional navigation structure

Estimated Time: 60-75 minutes

Prerequisites: Modules 1-5 completed (React hooks, TypeScript, component patterns)

In This Lesson

🌐 Understanding Single Page Applications

Before we touch a single line of React Router, let's build the right mental model. Understanding what problem routing solves makes every API in this lesson feel obvious instead of arbitrary.

What is a Single Page Application?

A Single Page Application (SPA) is a web app that loads one HTML document up front, then rewrites the page's content on the fly as you interact with it. Compare that to a traditional Multi-Page Application (MPA) β€” think of an old-school news site β€” where every click asks the server for a brand-new HTML page and the browser throws away everything and repaints from scratch.

Here's the everyday analogy: an MPA is like ordering at a restaurant where the kitchen clears your entire table and resets it for every course. An SPA is like a chef who simply swaps out the plate in front of you β€” same table, same tablecloth, same silverware, just new food. Faster, smoother, and nothing you were using disappears. 🍽️

graph TD A["User clicks link"] --> B{"App type?"} B -->|"Traditional MPA"| C["Full page reload"] B -->|"Modern SPA"| D["Client-side navigation"] C --> E["Server sends new HTML"] C --> F["Browser parses and renders"] C --> G["Slow, full refresh"] D --> H["Update URL"] D --> I["Render new component"] D --> J["Fast, smooth transition"] style A fill:#667eea,color:#fff style D fill:#48bb78,color:#fff style C fill:#fc8181,color:#fff

Traditional Multi-Page vs Single Page Apps

Neither approach is universally "better" β€” they trade off different things. Here's how they stack up:

Aspect Traditional MPA Modern SPA
Navigation Full page reload Instant, no reload
User Experience Slower, jarring transitions Smooth, app-like feel
Server Load Higher (every navigation) Lower (initial load only)
SEO Easier (native) Requires configuration (or SSR)
Initial Load Faster Slower (loads the app bundle)
State Management Simpler (resets each page) Richer (state persists across views)

πŸ“– Key Concept: Client-Side Routing

Client-side routing means the URL in the address bar changes, but no request is sent to the server. JavaScript intercepts the navigation, updates the URL through the browser's History API, and swaps in the component that matches the new path. The result: the illusion of many pages while you never actually leave the one page you loaded.

Why Do We Need React Router?

Here's a surprise for newcomers: React itself has no concept of URLs or pages. Out of the box, a React app is one component tree rendered into one <div>. Without a router, you'd end up hand-rolling something like const [page, setPage] = useState('home') and a giant if/else to decide what to show β€” and you'd get no working back button, no bookmarkable URLs, and no shareable links. That gets painful fast.

React Router is the community-standard library that fills this gap. It connects the browser's URL to your component tree, giving you:

  • πŸ—ΊοΈ URL-based navigation: Different URLs display different components
  • πŸ”— Linkable pages: Users can bookmark and share specific views
  • ⬅️ Browser history: Back and forward buttons work as expected
  • πŸ“± Deep linking: Direct access to any part of your app
  • 🎯 Code organization: Separate concerns by route/page

πŸ’‘ Real-World Example

Think of Netflix: when you click on a movie, the URL changes to something like /watch/12345, but the page doesn't reload. The video player component appears instantly. When you browse back to the home page, it's immediate. That's the power of client-side routing!

How React Router Works

React Router works by:

sequenceDiagram participant U as User participant B as Browser participant RR as React Router participant R as React U->>B: Clicks link B->>RR: URL changes RR->>RR: Matches URL to route RR->>R: Renders matched component R->>B: Updates DOM B->>U: Shows new view Note over RR: No server request!
  1. Listening to URL changes: React Router watches for URL changes using the browser's History API
  2. Matching routes: It compares the current URL against your defined routes
  3. Rendering components: When a match is found, it renders the corresponding component
  4. Managing history: It keeps track of navigation so back/forward buttons work

At its heart, a router is just a lookup table from URL to component. Picture it like the receptionist directory in an office lobby β€” a URL walks in, the router reads the directory, and points it to exactly the right room:

URL to Component Mapping Three URLs on the left each map through the router to a matching page component on the right URL Path Component / /about /blog/42 Router matches path <Home /> <About /> <BlogPost /> One URL in, one component out.
Figure 1: A router is a lookup table β€” each URL path resolves to exactly one component to render.

βœ… Benefits of React Router

  • Declarative: Define routes using JSX components
  • Dynamic: Routes can have parameters and nested structures
  • Type-Safe: Full TypeScript support
  • Flexible: Supports multiple routing strategies
  • Well-Maintained: Industry standard with great documentation

πŸ“¦ Installing and Setting Up React Router

Let's get React Router installed and configured in your TypeScript project.

Installation

React Router v7 is the current version (released in late 2024 and the standard throughout 2026). The good news: everything you learn here is API-compatible with v6.4+, so the concepts transfer directly to any recent project. Install it with your package manager of choice:

# Using npm
npm install react-router-dom

# Using yarn
yarn add react-router-dom

# TypeScript types ship inside the package (v6+).
# You do NOT need @types/react-router-dom anymore.

⚠️ Version Note

This lesson targets React Router v7 with React 19 and TypeScript 5.x. The v7 API is a smooth continuation of v6, so v6.4+ code works unchanged. The big break was v5 β†’ v6: if you meet older tutorials using <Switch> or component={Home}, those are outdated β€” v6/v7 use <Routes> and element={<Home />}. Confirm your version any time with npm list react-router-dom.

A note on names: the React Router team also ships a full-stack "framework mode" (the evolution of Remix) with its own file-based conventions. This lesson stays in library mode β€” plain components and hooks you drop into any React app. That's all you need to build great SPAs.

Project Structure for Routing

Before adding routes, let's organize our project structure. Here's a recommended folder layout:

src/
β”œβ”€β”€ App.tsx                 # Main app component with router setup
β”œβ”€β”€ main.tsx               # Entry point
β”œβ”€β”€ pages/                 # Page components (route destinations)
β”‚   β”œβ”€β”€ Home.tsx
β”‚   β”œβ”€β”€ About.tsx
β”‚   β”œβ”€β”€ Contact.tsx
β”‚   β”œβ”€β”€ NotFound.tsx
β”‚   └── ...
β”œβ”€β”€ components/            # Reusable components
β”‚   β”œβ”€β”€ Navbar.tsx
β”‚   β”œβ”€β”€ Footer.tsx
β”‚   └── ...
β”œβ”€β”€ layouts/              # Layout components
β”‚   └── MainLayout.tsx
└── types/                # TypeScript types
    └── index.ts

πŸ“– Naming Convention

Pages vs Components: We put route components (pages) in a separate pages/ folder to distinguish them from reusable components. This makes it clear which components are tied to specific routes.

Two Ways to Set Up Routing

Modern React Router gives you two styles for defining routes, and it's worth knowing both:

  • The declarative approach β€” wrap your app in <BrowserRouter> and describe routes with JSX <Routes>/<Route>. Simple, readable, perfect for learning and small-to-medium apps. We'll use it for most of this lesson.
  • The data router β€” build a router object with createBrowserRouter and render a single <RouterProvider>. This is the newer, recommended style for larger apps because it unlocks data loading, actions, and better error handling (topics for later lessons). Same routing concepts underneath.

Think of them as two doors into the same house β€” pick whichever fits your project. Let's start with the declarative door.

Basic Router Setup (Declarative)

The foundation of the declarative approach is the <BrowserRouter> component. It must wrap your entire application so routing is available everywhere inside it.

Update your src/main.tsx (or src/index.tsx). Note the React 19 createRoot entry point:

import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';

createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

πŸ’‘ Why Wrap in main.tsx?

We wrap the app with <BrowserRouter> at the very top (the entry point) so routing is available throughout the entire tree. Any component, no matter how deeply nested, can then use React Router's hooks and components. It's like installing the plumbing before you build the rooms β€” every faucet downstream just works.

Alternative Setup: The Data Router

Here's the same app wired up with the modern data router. Instead of nesting <Route> elements in JSX, you describe your routes as a plain array of objects and hand them to createBrowserRouter:

// src/main.tsx β€” data router style
import React from 'react';
import { createRoot } from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';

const router = createBrowserRouter([
  { path: '/', element: <Home /> },
  { path: '/about', element: <About /> },
  { path: '/contact', element: <Contact /> },
  { path: '*', element: <NotFound /> },
]);

createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

βœ… Which Should You Use?

For this lesson and for learning, the declarative <Routes>/<Route> style is the friendliest β€” you can see the whole route tree as JSX. Reach for the createBrowserRouter data router when your app grows and you want route-level data loading, form actions, and richer error handling. Both use the exact same path-matching rules, so nothing you learn here goes to waste.

Alternative: HashRouter

React Router also offers HashRouter as an alternative to BrowserRouter:

import { HashRouter } from 'react-router-dom';

// URLs will look like: http://example.com/#/about
<HashRouter>
  <App />
</HashRouter>
Router Type URL Format When to Use
BrowserRouter /about Modern apps with server support
HashRouter /#/about Static hosting (GitHub Pages)

βœ… Recommendation

Use BrowserRouter for most projects. It creates cleaner URLs and is the standard for modern web applications. Only use HashRouter if you're deploying to static hosting that doesn't support server-side routing configuration.

πŸ›£οΈ Creating Your First Routes

Now that React Router is installed, let's create some pages and define routes for them.

Creating Page Components

First, let's create simple page components. Create these files in src/pages/:

src/pages/Home.tsx:

import React from 'react';

function Home() {
  return (
    <div>
      <h1>🏠 Welcome Home</h1>
      <p>This is the home page of our application.</p>
      <p>Navigate to other pages using the links above!</p>
    </div>
  );
}

export default Home;

src/pages/About.tsx:

import React from 'react';

function About() {
  return (
    <div>
      <h1>πŸ“– About Us</h1>
      <p>Learn more about our company and mission.</p>
      <ul>
        <li>Founded in 2024</li>
        <li>Building amazing React applications</li>
        <li>Powered by TypeScript</li>
      </ul>
    </div>
  );
}

export default About;

src/pages/Contact.tsx:

import React from 'react';

function Contact() {
  return (
    <div>
      <h1>πŸ“§ Contact Us</h1>
      <p>Get in touch with our team!</p>
      <form>
        <div>
          <label htmlFor="name">Name:</label>
          <input type="text" id="name" name="name" />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input type="email" id="email" name="email" />
        </div>
        <div>
          <label htmlFor="message">Message:</label>
          <textarea id="message" name="message" rows={4}></textarea>
        </div>
        <button type="submit">Send Message</button>
      </form>
    </div>
  );
}

export default Contact;

src/pages/NotFound.tsx:

import React from 'react';
import { Link } from 'react-router-dom';

function NotFound() {
  return (
    <div style={{ textAlign: 'center', padding: '3rem' }}>
      <h1>404 - Page Not Found</h1>
      <p>The page you're looking for doesn't exist.</p>
      <Link to="/">← Go back home</Link>
    </div>
  );
}

export default NotFound;

πŸ’‘ Page Component Pattern

Notice that page components are just regular React components. The only difference is their purpose: they represent entire pages/views in your application, not reusable UI pieces.

Defining Routes with Routes and Route

Now let's wire up these pages to URLs. Update your src/App.tsx:

import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';

function App() {
  return (
    <div className="app">
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

export default App;

πŸ“– Understanding the Code

  • <Routes>: Container for all your route definitions
  • <Route>: Defines a single route mapping
  • path: The URL path to match
  • element: The component to render when the path matches (note: element={<Home />}, an actual JSX element β€” not the old v5 component={Home} prop)
  • path="*": Wildcard that matches any unmatched route (your 404 page)

How Route Matching Works

When the URL changes, React Router looks at all your <Route> definitions, scores each one for how well it matches, and renders the single best match. For a simple app you can picture it walking the list until something fits:

flowchart TD A["URL: /about"] --> B["Check routes"] B --> C["path = / ?"] C -->|"No match"| D["path = /about ?"] D -->|"Match!"| E["Render About component"] D -->|"No match"| F["path = /contact ?"] F -->|"No match"| G["path = * ?"] G -->|"Match!"| H["Render NotFound"] style E fill:#48bb78,color:#fff style H fill:#fc8181,color:#fff

⚠️ Route Order Matters (Sort of)

Unlike the old v5 <Switch> (which took the first textual match), React Router v6/v7 ranks routes by specificity, so a more specific path wins even if it's listed later. In practice you rarely fight ordering β€” just remember the wildcard path="*" is your catch-all 404 and belongs conceptually last.

Testing Your Routes

Start your development server and test the routes:

npm run dev
# or
yarn dev

Try navigating to these URLs manually in your browser:

  • http://localhost:5173/ β†’ Should show Home page
  • http://localhost:5173/about β†’ Should show About page
  • http://localhost:5173/contact β†’ Should show Contact page
  • http://localhost:5173/doesnotexist β†’ Should show 404 page

βœ… Success!

If you can manually navigate to these URLs and see the correct pages, your routing is working! However, you'll notice that changing URLs in the address bar causes full page reloads. In the next section, we'll add proper navigation links to avoid this.

πŸ”— Navigation with Link and NavLink

Manually typing URLs in the browser isn't a great user experience. Let's add proper navigation links that work without full page reloads.

The Problem with Regular Anchor Tags

You might be tempted to use regular HTML <a> tags for navigation:

// ❌ DON'T DO THIS
function Navbar() {
  return (
    <nav>
      <a href="/">Home</a>
      <a href="/about">About</a>
      <a href="/contact">Contact</a>
    </nav>
  );
}

The problem: Regular <a> tags cause full page reloads, defeating the purpose of a Single Page Application. Your React app will restart from scratch on every navigation!

⚠️ Why Full Reloads Are Bad

  • Loses all component state
  • Slower user experience
  • Re-downloads JavaScript bundles
  • Triggers unnecessary re-initialization
  • Wastes bandwidth and server resources

Using the Link Component

React Router provides the Link component for client-side navigation:

import React from 'react';
import { Link } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Link to="/contact">Contact</Link>
    </nav>
  );
}

export default Navbar;

πŸ“– How Link Works

Link renders as a real <a> tag in the DOM (great for accessibility and "open in new tab"), but React Router intercepts left-clicks, calls preventDefault(), and updates the URL through the History API. That swaps in the new route component without a page reload β€” you get the best of both worlds.

Link Props and Options

The Link component accepts several useful props:

import { Link } from 'react-router-dom';

function NavigationExample() {
  return (
    <div>
      {/* Basic link */}
      <Link to="/about">About Us</Link>
      
      {/* Link with relative path */}
      <Link to="../parent">Go to Parent</Link>
      
      {/* Link with state (pass data to next page) */}
      <Link 
        to="/profile" 
        state={{ from: 'homepage', userId: 123 }}
      >
        View Profile
      </Link>
      
      {/* Link that replaces history entry */}
      <Link to="/login" replace>
        Login
      </Link>
      
      {/* Link with additional props */}
      <Link 
        to="/docs"
        className="nav-link"
        target="_blank"
        rel="noopener noreferrer"
      >
        Documentation
      </Link>
    </div>
  );
}
Prop Type Description
to string | Partial<Path> Destination path (required)
replace boolean Replace current history entry instead of pushing
state unknown Pass data to the destination route
relative "route" | "path" How relative paths are resolved

Introducing NavLink

NavLink is a special version of Link that adds styling to the active link. This is perfect for navigation menus where you want to highlight the current page.

import React from 'react';
import { NavLink } from 'react-router-dom';
import './Navbar.css';

function Navbar() {
  return (
    <nav className="navbar">
      <NavLink 
        to="/"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        Home
      </NavLink>
      
      <NavLink 
        to="/about"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        About
      </NavLink>
      
      <NavLink 
        to="/contact"
        className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
      >
        Contact
      </NavLink>
    </nav>
  );
}

export default Navbar;

And the corresponding CSS (Navbar.css):

.navbar {
  display: flex;
  gap: 1rem;
  padding: 1rem;
  background: #667eea;
}

.nav-link {
  color: white;
  text-decoration: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  transition: background-color 0.2s;
}

.nav-link:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

.nav-link.active {
  background-color: rgba(255, 255, 255, 0.2);
  font-weight: bold;
}

πŸ’‘ NavLink Features

NavLink provides additional props and features:

  • isActive: Boolean passed to className/style functions
  • isPending: Boolean for loading states (v6.4+)
  • end: Only match exact path (no child routes)
  • caseSensitive: Make path matching case-sensitive

NavLink Style Options

You can style NavLink in multiple ways:

import { NavLink } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      {/* Option 1: className function */}
      <NavLink
        to="/"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        Home
      </NavLink>
      
      {/* Option 2: style function */}
      <NavLink
        to="/about"
        style={({ isActive }) => ({
          color: isActive ? '#667eea' : '#333',
          fontWeight: isActive ? 'bold' : 'normal'
        })}
      >
        About
      </NavLink>
      
      {/* Option 3: children function (advanced) */}
      <NavLink to="/contact">
        {({ isActive }) => (
          <span>
            {isActive && '➜ '}
            Contact
          </span>
        )}
      </NavLink>
    </nav>
  );
}

Complete Navbar Example

Let's create a full-featured navbar component. Create src/components/Navbar.tsx:

import React from 'react';
import { NavLink } from 'react-router-dom';
import './Navbar.css';

function Navbar() {
  // Helper function to determine active class
  const getNavLinkClass = ({ isActive }: { isActive: boolean }) => {
    return isActive ? 'nav-link nav-link-active' : 'nav-link';
  };
  
  return (
    <nav className="navbar">
      <div className="navbar-brand">
        <NavLink to="/" className="brand-link">
          πŸš€ My App
        </NavLink>
      </div>
      
      <div className="navbar-links">
        <NavLink to="/" end className={getNavLinkClass}>
          Home
        </NavLink>
        <NavLink to="/about" className={getNavLinkClass}>
          About
        </NavLink>
        <NavLink to="/contact" className={getNavLinkClass}>
          Contact
        </NavLink>
      </div>
    </nav>
  );
}

export default Navbar;

βœ… The end Prop

Notice the end prop on the Home link. Without it, / would match all paths (since all paths start with /). The end prop ensures it only matches the exact path.

Integrating Navbar into App

Now let's add the navbar to our application. Update src/App.tsx:

import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
import './App.css';

function App() {
  return (
    <div className="app">
      <Navbar />
      
      <main className="main-content">
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </main>
    </div>
  );
}

export default App;

Add some basic styling in src/App.css:

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.main-content {
  flex: 1;
  padding: 2rem;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}

πŸ‹οΈ Exercise: Add a Footer with Links

Create a Footer component that appears on every page with links to Privacy Policy and Terms of Service pages.

πŸ’‘ Hint

Create Footer.tsx in the components folder, use Link components for navigation, and add it to App.tsx below the Routes.

βœ… Solution
// src/components/Footer.tsx
import React from 'react';
import { Link } from 'react-router-dom';
import './Footer.css';

function Footer() {
  return (
    <footer className="footer">
      <div className="footer-content">
        <p>© 2024 My App. All rights reserved.</p>
        <div className="footer-links">
          <Link to="/privacy">Privacy Policy</Link>
          <Link to="/terms">Terms of Service</Link>
        </div>
      </div>
    </footer>
  );
}

export default Footer;

// In App.tsx, add after main:
// <Footer />

πŸ“ Typing Routes with TypeScript

One of the benefits of using React Router with TypeScript is type safety. Let's explore how to properly type your routing code.

Typing Route Parameters

When you have dynamic routes (which we'll cover more in the next lesson), you'll want to type the parameters. Here's a preview:

import { useParams } from 'react-router-dom';

function UserProfile() {
  // Parameterize useParams with the exact keys you expect
  const { userId } = useParams<{ userId: string }>();

  // TypeScript now knows userId is string | undefined
  if (!userId) {
    return <div>User ID is required</div>;
  }

  return <div>User Profile: {userId}</div>;
}

export default UserProfile;

πŸ“– Why string | undefined?

Route parameters are always string | undefined, never a number and never guaranteed to exist. Why? Because a param is just a slice of the URL text, and a user can type any URL by hand. TypeScript refuses to lie to you about it β€” so you narrow it with a quick guard (like the if (!userId) check above) before using it. This tiny habit prevents a whole class of "cannot read property of undefined" crashes.

Programmatic Navigation with useNavigate

Links are for things the user clicks. But sometimes your code needs to move the user β€” after a form submits, once login succeeds, or when a timer runs out. That's the job of the useNavigate hook, which hands you a fully-typed navigate function:

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();

  const handleSubmit = async (event: React.FormEvent) => {
    event.preventDefault();
    await logIn();
    // Push a new entry onto history and go to the dashboard
    navigate('/dashboard');

    // Other handy options:
    // navigate('/login', { replace: true });        // replace instead of push
    // navigate(-1);                                  // go back one step
    // navigate('/profile', { state: { from: 'login' } }); // pass state
  };

  return <form onSubmit={handleSubmit}>{/* fields... */}</form>;
}

πŸ’‘ Link vs. useNavigate

Rule of thumb: if the user is choosing where to go, render a <Link> β€” it's accessible and keyboard-friendly out of the box. If your logic decides where to go (after saving, redirecting, or reacting to an event), call navigate(). Don't reach for useNavigate just to make a clickable link.

Creating a Routes Configuration

For larger applications, it's helpful to centralize your route definitions with proper types:

// src/types/routes.ts

export const ROUTES = {
  HOME: '/',
  ABOUT: '/about',
  CONTACT: '/contact',
  PROFILE: '/profile/:userId',
  BLOG: '/blog',
  BLOG_POST: '/blog/:postId',
  NOT_FOUND: '*'
} as const;

// Type for route keys
export type RouteKey = keyof typeof ROUTES;

// Type for route values
export type RoutePath = typeof ROUTES[RouteKey];

Now use these constants throughout your app:

// In App.tsx
import { Routes, Route } from 'react-router-dom';
import { ROUTES } from './types/routes';
import Home from './pages/Home';
import About from './pages/About';
// ... other imports

function App() {
  return (
    <Routes>
      <Route path={ROUTES.HOME} element={<Home />} />
      <Route path={ROUTES.ABOUT} element={<About />} />
      <Route path={ROUTES.CONTACT} element={<Contact />} />
      <Route path={ROUTES.NOT_FOUND} element={<NotFound />} />
    </Routes>
  );
}

// In components
import { Link } from 'react-router-dom';
import { ROUTES } from '../types/routes';

function Navbar() {
  return (
    <nav>
      <Link to={ROUTES.HOME}>Home</Link>
      <Link to={ROUTES.ABOUT}>About</Link>
    </nav>
  );
}

βœ… Benefits of Route Constants

  • Type Safety: TypeScript catches typos at compile time
  • Refactoring: Change a route in one place
  • Autocomplete: IDE suggestions for routes
  • Documentation: All routes visible in one file
  • Consistency: Everyone uses the same paths

Typing Link and NavLink Props

For reusable navigation components, you can type the props:

import React from 'react';
import { Link, LinkProps } from 'react-router-dom';

// Extend LinkProps for custom behavior
interface CustomLinkProps extends LinkProps {
  icon?: string;
  badge?: number;
}

function CustomLink({ icon, badge, children, ...linkProps }: CustomLinkProps) {
  return (
    <Link {...linkProps} className="custom-link">
      {icon && <span className="icon">{icon}</span>}
      {children}
      {badge && badge > 0 && (
        <span className="badge">{badge}</span>
      )}
    </Link>
  );
}

// Usage
<CustomLink to="/messages" icon="πŸ“§" badge={5}>
  Messages
</CustomLink>

Typing Router State

When passing state through navigation, type it properly:

import { Link, useLocation } from 'react-router-dom';

// Define state shape
interface LocationState {
  from: string;
  userId?: number;
}

// Passing state
function HomePage() {
  return (
    <Link 
      to="/profile"
      state={{ from: 'home', userId: 123 } as LocationState}
    >
      View Profile
    </Link>
  );
}

// Receiving state
function ProfilePage() {
  const location = useLocation();
  const state = location.state as LocationState | null;
  
  return (
    <div>
      <h1>Profile</h1>
      {state && (
        <p>You came from: {state.from}</p>
      )}
    </div>
  );
}

⚠️ Type Assertion with State

Unfortunately, React Router doesn't provide type inference for location state, so you need to use type assertions (as). This is one of the few places where type assertions are appropriate. Always check if state exists before using it!

Creating Type-Safe Route Builders

For dynamic routes, create helper functions:

// src/utils/routes.ts

export const buildUserProfileRoute = (userId: string | number): string => {
  return `/profile/${userId}`;
};

export const buildBlogPostRoute = (postId: string | number): string => {
  return `/blog/${postId}`;
};

// Usage
import { Link } from 'react-router-dom';
import { buildUserProfileRoute } from '../utils/routes';

function UserList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          <Link to={buildUserProfileRoute(user.id)}>
            {user.name}
          </Link>
        </li>
      ))}
    </ul>
  );
}

πŸ‹οΈ Exercise: Create Route Type Definitions

Create a comprehensive routes configuration file with constants for all your routes and helper functions for dynamic routes.

πŸ’‘ Hint

Define an object with all route paths, export it as const, and create builder functions for routes with parameters.

βœ… Solution
// src/config/routes.ts

export const ROUTES = {
  HOME: '/',
  ABOUT: '/about',
  CONTACT: '/contact',
  BLOG: '/blog',
  BLOG_POST: '/blog/:postId',
  USER_PROFILE: '/user/:userId',
  SETTINGS: '/settings',
  NOT_FOUND: '*'
} as const;

export const buildBlogPostRoute = (postId: string | number) => 
  `/blog/${postId}`;

export const buildUserProfileRoute = (userId: string | number) => 
  `/user/${userId}`;

export type RouteKey = keyof typeof ROUTES;

πŸ—οΈ Building a Multi-Page Application

Let's put everything together and build a complete multi-page application with proper routing, navigation, and TypeScript types.

Project Structure

Here's the complete structure we'll build:

src/
β”œβ”€β”€ App.tsx
β”œβ”€β”€ main.tsx
β”œβ”€β”€ config/
β”‚   └── routes.ts           # Route constants
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ Navbar.tsx          # Navigation component
β”‚   β”œβ”€β”€ Navbar.css
β”‚   β”œβ”€β”€ Footer.tsx          # Footer component
β”‚   └── Footer.css
β”œβ”€β”€ pages/
β”‚   β”œβ”€β”€ Home.tsx
β”‚   β”œβ”€β”€ About.tsx
β”‚   β”œβ”€β”€ Contact.tsx
β”‚   β”œβ”€β”€ Services.tsx        # New page
β”‚   β”œβ”€β”€ Blog.tsx            # New page
β”‚   └── NotFound.tsx
└── types/
    └── index.ts            # Shared types

Step 1: Define Routes Configuration

Create src/config/routes.ts:

export const ROUTES = {
  HOME: '/',
  ABOUT: '/about',
  SERVICES: '/services',
  BLOG: '/blog',
  CONTACT: '/contact',
  NOT_FOUND: '*'
} as const;

export type RouteKey = keyof typeof ROUTES;
export type RoutePath = typeof ROUTES[RouteKey];

Step 2: Create Additional Pages

src/pages/Services.tsx:

import React from 'react';

function Services() {
  const services = [
    { id: 1, name: 'Web Development', icon: 'πŸ’»', description: 'Custom websites and web applications' },
    { id: 2, name: 'Mobile Apps', icon: 'πŸ“±', description: 'iOS and Android development' },
    { id: 3, name: 'UI/UX Design', icon: '🎨', description: 'Beautiful and intuitive interfaces' },
    { id: 4, name: 'Consulting', icon: 'πŸ’Ό', description: 'Technical consulting services' }
  ];

  return (
    <div>
      <h1>πŸ› οΈ Our Services</h1>
      <p>We offer a wide range of professional services to help your business grow.</p>
      
      <div className="services-grid">
        {services.map(service => (
          <div key={service.id} className="service-card">
            <div className="service-icon">{service.icon}</div>
            <h3>{service.name}</h3>
            <p>{service.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

export default Services;

src/pages/Blog.tsx:

import React from 'react';
import { Link } from 'react-router-dom';

interface BlogPost {
  id: number;
  title: string;
  excerpt: string;
  date: string;
  author: string;
}

function Blog() {
  const posts: BlogPost[] = [
    {
      id: 1,
      title: 'Getting Started with React Router',
      excerpt: 'Learn the basics of routing in React applications...',
      date: '2024-01-15',
      author: 'Jane Doe'
    },
    {
      id: 2,
      title: 'TypeScript Best Practices',
      excerpt: 'Improve your TypeScript code with these tips...',
      date: '2024-01-10',
      author: 'John Smith'
    },
    {
      id: 3,
      title: 'Building SPAs with React',
      excerpt: 'A comprehensive guide to single-page applications...',
      date: '2024-01-05',
      author: 'Jane Doe'
    }
  ];

  return (
    <div>
      <h1>πŸ“ Blog</h1>
      <p>Stay updated with our latest articles and tutorials.</p>
      
      <div className="blog-posts">
        {posts.map(post => (
          <article key={post.id} className="blog-post-card">
            <h2>{post.title}</h2>
            <div className="post-meta">
              <span>By {post.author}</span>
              <span>{new Date(post.date).toLocaleDateString()}</span>
            </div>
            <p>{post.excerpt}</p>
            <Link to={`/blog/${post.id}`}>Read more β†’</Link>
          </article>
        ))}
      </div>
    </div>
  );
}

export default Blog;

Step 3: Enhanced Navbar

Update src/components/Navbar.tsx with all routes:

import React from 'react';
import { NavLink } from 'react-router-dom';
import { ROUTES } from '../config/routes';
import './Navbar.css';

function Navbar() {
  const navLinks = [
    { to: ROUTES.HOME, label: 'Home', end: true },
    { to: ROUTES.ABOUT, label: 'About' },
    { to: ROUTES.SERVICES, label: 'Services' },
    { to: ROUTES.BLOG, label: 'Blog' },
    { to: ROUTES.CONTACT, label: 'Contact' }
  ];

  return (
    <nav className="navbar">
      <div className="navbar-container">
        <NavLink to={ROUTES.HOME} className="navbar-brand">
          πŸš€ My App
        </NavLink>
        
        <div className="navbar-links">
          {navLinks.map(link => (
            <NavLink
              key={link.to}
              to={link.to}
              end={link.end}
              className={({ isActive }) => 
                isActive ? 'nav-link nav-link-active' : 'nav-link'
              }
            >
              {link.label}
            </NavLink>
          ))}
        </div>
      </div>
    </nav>
  );
}

export default Navbar;

Step 4: Complete App Component

Update src/App.tsx with all routes:

import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { ROUTES } from './config/routes';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './pages/Home';
import About from './pages/About';
import Services from './pages/Services';
import Blog from './pages/Blog';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
import './App.css';

function App() {
  return (
    <div className="app">
      <Navbar />
      
      <main className="main-content">
        <Routes>
          <Route path={ROUTES.HOME} element={<Home />} />
          <Route path={ROUTES.ABOUT} element={<About />} />
          <Route path={ROUTES.SERVICES} element={<Services />} />
          <Route path={ROUTES.BLOG} element={<Blog />} />
          <Route path={ROUTES.CONTACT} element={<Contact />} />
          <Route path={ROUTES.NOT_FOUND} element={<NotFound />} />
        </Routes>
      </main>
      
      <Footer />
    </div>
  );
}

export default App;

Step 5: Styling

Add comprehensive styling in src/App.css:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  line-height: 1.6;
  color: #333;
}

.app {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.main-content {
  flex: 1;
  padding: 2rem;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}

/* Services Grid */
.services-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 2rem;
  margin-top: 2rem;
}

.service-card {
  padding: 2rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  text-align: center;
  transition: transform 0.2s, box-shadow 0.2s;
}

.service-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.service-icon {
  font-size: 3rem;
  margin-bottom: 1rem;
}

/* Blog Posts */
.blog-posts {
  display: flex;
  flex-direction: column;
  gap: 2rem;
  margin-top: 2rem;
}

.blog-post-card {
  padding: 1.5rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
}

.blog-post-card h2 {
  margin-bottom: 0.5rem;
  color: #667eea;
}

.post-meta {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: #666;
  margin-bottom: 1rem;
}

.blog-post-card a {
  color: #667eea;
  text-decoration: none;
  font-weight: 500;
}

.blog-post-card a:hover {
  text-decoration: underline;
}

@media (max-width: 768px) {
  .main-content {
    padding: 1rem;
  }
  
  .services-grid {
    grid-template-columns: 1fr;
  }
}

βœ… What We've Built

You now have a fully functional multi-page application with:

  • βœ… Five different pages with unique content
  • βœ… Type-safe route configuration
  • βœ… Active link highlighting in navigation
  • βœ… Proper application structure
  • βœ… Responsive design
  • βœ… 404 error handling

Play With Route Matching

Given the routes we just defined, which component renders for a given URL? Click a URL below to see what React Router would match. Notice how anything that doesn't match a real route falls through to the wildcard 404.

<Route path="..." element={<Page />} /> β€” click a URL to resolve it

πŸ‘† Click a URL above to see which component renders.

πŸ‹οΈ Exercise: Add a Products Page

Create a new Products page that displays a list of products. Add it to your routes configuration and navigation menu.

πŸ’‘ Requirements
  • Create Products.tsx in pages/
  • Display at least 4 products with name, price, image
  • Add PRODUCTS route to routes.ts
  • Add Products link to Navbar
  • Add Products route to App.tsx
βœ… Solution Outline
// 1. Update routes.ts
export const ROUTES = {
  // ... existing routes
  PRODUCTS: '/products',
  // ...
};

// 2. Create Products.tsx
const products = [
  { id: 1, name: 'Product 1', price: 29.99, image: '🎁' },
  // ... more products
];

// 3. Add to Navbar.tsx navLinks array
{ to: ROUTES.PRODUCTS, label: 'Products' }

// 4. Add to App.tsx Routes
<Route path={ROUTES.PRODUCTS} element={<Products />} />

✨ Best Practices and Common Patterns

Let's explore some best practices and common patterns when working with React Router to build maintainable and scalable applications.

1. Organize Routes by Feature

As your application grows, organize routes by feature rather than keeping everything in one file:

src/
β”œβ”€β”€ features/
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”œβ”€β”€ routes.tsx        # Auth-related routes
β”‚   β”‚   β”œβ”€β”€ Login.tsx
β”‚   β”‚   └── Register.tsx
β”‚   β”œβ”€β”€ blog/
β”‚   β”‚   β”œβ”€β”€ routes.tsx        # Blog-related routes
β”‚   β”‚   β”œβ”€β”€ BlogList.tsx
β”‚   β”‚   └── BlogPost.tsx
β”‚   └── dashboard/
β”‚       β”œβ”€β”€ routes.tsx        # Dashboard routes
β”‚       └── Dashboard.tsx
└── App.tsx                   # Main app with route imports

Example feature routes file (features/blog/routes.tsx):

import { Route } from 'react-router-dom';
import BlogList from './BlogList';
import BlogPost from './BlogPost';

export const blogRoutes = (
  <>
    <Route path="/blog" element={<BlogList />} />
    <Route path="/blog/:postId" element={<BlogPost />} />
  </>
);

// In App.tsx
import { Routes } from 'react-router-dom';
import { blogRoutes } from './features/blog/routes';
import { authRoutes } from './features/auth/routes';

function App() {
  return (
    <Routes>
      {blogRoutes}
      {authRoutes}
      {/* ... other routes */}
    </Routes>
  );
}

βœ… Benefits of Feature-Based Routes

  • Scalability: Easy to add new features without cluttering App.tsx
  • Maintainability: Routes live with related components
  • Team Collaboration: Different teams can work on different features
  • Code Splitting: Easier to implement lazy loading per feature

2. Nested Routes and Shared Layouts with <Outlet>

Here's a pattern you'll use constantly: a group of pages that share the same chrome β€” a dashboard with a sidebar, say, where only the middle panel changes. Instead of repeating that sidebar in every page, you nest child routes inside a layout route and mark the swap-in spot with <Outlet />.

Think of <Outlet> as a picture frame. The layout is the frame (and the wall around it); the matched child route is the photo you slot in. Change routes, swap the photo β€” the frame stays put.

graph TD A["/dashboard (DashboardLayout + Outlet)"] --> B["index β†’ Overview"] A --> C["settings β†’ Settings"] A --> D["billing β†’ Billing"] style A fill:#667eea,color:#fff style B fill:#48bb78,color:#fff
import { Routes, Route, Outlet, NavLink } from 'react-router-dom';
import Overview from './Overview';
import Settings from './Settings';
import Billing from './Billing';

// A layout component renders shared UI + an Outlet for the active child
function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside>
        <NavLink to="/dashboard" end>Overview</NavLink>
        <NavLink to="/dashboard/settings">Settings</NavLink>
        <NavLink to="/dashboard/billing">Billing</NavLink>
      </aside>
      <main>
        {/* The matched child route renders right here */}
        <Outlet />
      </main>
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/dashboard" element={<DashboardLayout />}>
        {/* Index route: shown at exactly /dashboard */}
        <Route index element={<Overview />} />
        {/* Child routes: shown at /dashboard/settings, /dashboard/billing */}
        <Route path="settings" element={<Settings />} />
        <Route path="billing" element={<Billing />} />
      </Route>
    </Routes>
  );
}

πŸ“– Index Routes and Outlet

An index route (<Route index ... />) is the default child β€” it renders into the parent's <Outlet /> when the URL matches the parent exactly (here, /dashboard with nothing after it). Child paths are written relative to the parent, so path="settings" becomes /dashboard/settings. This same layout-route idea is exactly how you could refactor our multi-page app to stop repeating <Navbar /> and <Footer /> in every page. We'll go deeper on nested routes in Lesson 6.2.

3. Create a Custom 404 Component

Make your 404 page helpful and on-brand:

import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import './NotFound.css';

function NotFound() {
  const location = useLocation();
  
  return (
    <div className="not-found-container">
      <div className="not-found-content">
        <h1 className="not-found-title">404</h1>
        <h2>Page Not Found</h2>
        <p>
          The page <code>{location.pathname}</code> doesn't exist.
        </p>
        
        <div className="not-found-actions">
          <Link to="/" className="button button-primary">
            Go to Homepage
          </Link>
          <button 
            onClick={() => window.history.back()}
            className="button button-secondary"
          >
            Go Back
          </button>
        </div>
        
        <div className="helpful-links">
          <h3>Popular Pages:</h3>
          <ul>
            <li><Link to="/about">About Us</Link></li>
            <li><Link to="/contact">Contact</Link></li>
            <li><Link to="/blog">Blog</Link></li>
          </ul>
        </div>
      </div>
    </div>
  );
}

export default NotFound;

4. Loading States for Route Transitions

Show feedback during route changes (especially important with lazy loading):

import React, { Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

// Lazy load components
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));

function LoadingSpinner() {
  return (
    <div className="loading-container">
      <div className="spinner"></div>
      <p>Loading...</p>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  );
}

πŸ’‘ Why Lazy Loading?

Lazy loading (code splitting) loads route components only when needed, reducing initial bundle size. This makes your app load faster, especially important for users on slow connections.

5. Scroll to Top on Route Change

By default, React Router doesn't scroll to the top when navigating. Create a component to handle this:

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function ScrollToTop() {
  const { pathname } = useLocation();
  
  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);
  
  return null;
}

// In App.tsx
function App() {
  return (
    <div className="app">
      <ScrollToTop />
      <Navbar />
      <Routes>
        {/* ... routes */}
      </Routes>
    </div>
  );
}

6. Use URL Parameters for Shareable State

Put important state in the URL so users can bookmark and share specific views:

// βœ… GOOD: State in URL
// URL: /products?category=electronics&sort=price&page=2

import { useSearchParams } from 'react-router-dom';

function Products() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const category = searchParams.get('category') || 'all';
  const sort = searchParams.get('sort') || 'name';
  const page = searchParams.get('page') || '1';
  
  const handleCategoryChange = (newCategory: string) => {
    setSearchParams({ category: newCategory, sort, page });
  };
  
  // Users can bookmark this URL and return to exact state!
  return (
    <div>
      <select value={category} onChange={e => handleCategoryChange(e.target.value)}>
        <option value="all">All</option>
        <option value="electronics">Electronics</option>
      </select>
      {/* ... */}
    </div>
  );
}
// ❌ BAD: State only in React
// URL: /products (no context)

import { useState } from 'react';

function Products() {
  const [category, setCategory] = useState('all');
  
  // State is lost on refresh!
  // Can't share this view with others!
}

⚠️ When to Use URL vs Local State

Use URL for: Filters, search queries, pagination, sort order - anything users might want to bookmark or share.

Use local state for: UI-only state like modals, tooltips, form inputs (before submission).

7. Breadcrumb Navigation

Create breadcrumbs to help users understand their location:

import { Link, useLocation } from 'react-router-dom';

function Breadcrumbs() {
  const location = useLocation();
  
  const pathnames = location.pathname.split('/').filter(x => x);
  
  return (
    <nav className="breadcrumbs">
      <Link to="/">Home</Link>
      {pathnames.map((name, index) => {
        const routeTo = `/${pathnames.slice(0, index + 1).join('/')}`;
        const isLast = index === pathnames.length - 1;
        
        return isLast ? (
          <span key={routeTo}> / {name}</span>
        ) : (
          <span key={routeTo}>
            {' / '}
            <Link to={routeTo}>{name}</Link>
          </span>
        );
      })}
    </nav>
  );
}

// Usage: Renders "Home / blog / post-title" for /blog/post-title

8. Route Configuration Array Pattern

For complex routing, use a configuration array:

import { RouteObject, useRoutes } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';

const routes: RouteObject[] = [
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/about',
    element: <About />,
  },
  {
    path: '/contact',
    element: <Contact />,
  },
  {
    path: '*',
    element: <NotFound />,
  }
];

function App() {
  const element = useRoutes(routes);
  return (
    <div className="app">
      <Navbar />
      {element}
    </div>
  );
}

βœ… Best Practices Summary

  • Organize routes by feature for scalability
  • Use index routes for default nested content
  • Create helpful 404 pages with navigation options
  • Implement loading states for better UX
  • Scroll to top on route changes
  • Store shareable state in URL parameters
  • Add breadcrumbs for complex navigation
  • Use route configuration arrays for complex apps
  • Keep route constants in a central location
  • Type everything with TypeScript

Common Pitfalls to Avoid

⚠️ Don't Use <a> Tags

Always use <Link> or <NavLink> for internal navigation. Regular <a> tags cause full page reloads.

⚠️ Don't Forget the Wildcard Route

Always include a path="*" route for 404 handling. Place it last in your route definitions.

⚠️ Don't Nest BrowserRouter

Only wrap your app once with <BrowserRouter> at the top level. Don't add additional routers in child components.

⚠️ Don't Store Everything in URL

Not all state belongs in the URL. Keep form inputs, modal states, and temporary UI state in local component state.

πŸ‹οΈ Exercise: Implement Best Practices

Enhance your multi-page app with three best practices from this section.

πŸ’‘ Suggested Improvements
  1. Add a ScrollToTop component
  2. Enhance the NotFound page with helpful links
  3. Add breadcrumb navigation
βœ… Implementation Tips
  • ScrollToTop: Create component, import useLocation and useEffect
  • NotFound: Use useLocation to show attempted path, add navigation options
  • Breadcrumbs: Parse location.pathname, create links for each segment

🎯 Summary and Next Steps

Congratulations! You've learned the fundamentals of React Router and built your first multi-page application.

What You've Learned

πŸŽ“ Key Concepts Mastered

  • SPAs vs MPAs: Understanding single-page applications and client-side routing
  • Installation: Setting up React Router v7 with React 19 and TypeScript
  • Two setups: Declarative <BrowserRouter>/<Routes>/<Route> and the createBrowserRouter + <RouterProvider> data router
  • Navigation: Using <Link>/<NavLink> and useNavigate for client-side navigation
  • Nesting: Shared layouts with <Outlet> and index routes
  • TypeScript: Typing routes, useParams, and navigation state
  • Best Practices: Professional patterns and common pitfalls

Complete Example: Everything Together

Here's a complete, production-ready routing setup:

// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

// src/config/routes.ts
export const ROUTES = {
  HOME: '/',
  ABOUT: '/about',
  SERVICES: '/services',
  BLOG: '/blog',
  CONTACT: '/contact',
  NOT_FOUND: '*'
} as const;

// src/App.tsx
import React from 'react';
import { Routes, Route } from 'react-router-dom';
import { ROUTES } from './config/routes';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import ScrollToTop from './components/ScrollToTop';
import Home from './pages/Home';
import About from './pages/About';
import Services from './pages/Services';
import Blog from './pages/Blog';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';

function App() {
  return (
    <div className="app">
      <ScrollToTop />
      <Navbar />
      <main className="main-content">
        <Routes>
          <Route path={ROUTES.HOME} element={<Home />} />
          <Route path={ROUTES.ABOUT} element={<About />} />
          <Route path={ROUTES.SERVICES} element={<Services />} />
          <Route path={ROUTES.BLOG} element={<Blog />} />
          <Route path={ROUTES.CONTACT} element={<Contact />} />
          <Route path={ROUTES.NOT_FOUND} element={<NotFound />} />
        </Routes>
      </main>
      <Footer />
    </div>
  );
}

export default App;

Quick Reference: React Router Essentials

Component/Hook Purpose Example
BrowserRouter Wrap app for routing (declarative) <BrowserRouter><App /></BrowserRouter>
createBrowserRouter Build a router from a routes array (data router) createBrowserRouter([{ path: '/', element: <Home /> }])
RouterProvider Render a data router <RouterProvider router={router} />
Routes / Route Container for / definition of a route <Route path="/" element={<Home />} />
Link Navigate to a route <Link to="/about">About</Link>
NavLink Link with active state <NavLink to="/">Home</NavLink>
Outlet Render the matched child route <Outlet />
useParams Read typed URL parameters const { userId } = useParams<{ userId: string }>()
useNavigate Programmatic navigation navigate('/about')

What's Next?

In the upcoming lessons, you'll learn:

πŸ“š Lesson 6.2: Advanced Routing

  • Nested routes and layouts
  • Dynamic route parameters
  • useParams and useNavigate hooks
  • Programmatic navigation
  • useLocation hook in depth

πŸ“š Lesson 6.3: Route Protection and Loading

  • Protected/private routes
  • Authentication flows
  • Lazy loading with React.lazy and Suspense
  • Code splitting strategies
  • Error boundaries with routing

πŸ“š Lesson 6.4: Search and Query Parameters

  • useSearchParams hook
  • Managing query strings
  • Filters and search in URL
  • Pagination with URL state

Practice Projects

To reinforce your learning, try building:

  1. Portfolio Website: Multi-page portfolio with About, Projects, Contact pages
  2. Documentation Site: Navigation between different doc sections
  3. Restaurant Menu: Different pages for menu categories
  4. Product Catalog: Browse different product categories

Additional Resources

πŸŽ‰ Congratulations!

You've mastered React Router basics!

You can now build multi-page React applications with professional, type-safe routing.

πŸ‹οΈ Final Challenge: Build a Complete Site

Create a complete website with:

  • At least 5 different pages
  • Navigation menu with active link highlighting
  • Footer with additional links
  • Custom 404 page
  • ScrollToTop functionality
  • Route constants in a config file
  • TypeScript types for all routes
  • Responsive design

Suggested Theme: Personal blog, business website, or hobby showcase