Routes and Links — Defining Pages and Navigation

Routes in React Router v6 are defined with <Route path="..." element={<Component />} /> inside a <Routes> container. The router matches the current URL against all route paths and renders the matching element. Nested routes allow a parent route to render a layout (header, sidebar) while child routes render the page-specific content — the parent uses <Outlet /> to mark where child routes render. Navigation uses <Link> for basic links and <NavLink> for links that need to know if they are the active route.

Basic Route Setup

// src/App.jsx
import { Routes, Route } from "react-router-dom";
import Layout         from "@/components/layout/Layout";
import HomePage       from "@/pages/HomePage";
import PostDetailPage from "@/pages/PostDetailPage";
import LoginPage      from "@/pages/LoginPage";
import RegisterPage   from "@/pages/RegisterPage";
import DashboardPage  from "@/pages/DashboardPage";
import NotFoundPage   from "@/pages/NotFoundPage";

export default function App() {
    return (
        <Routes>
            {/* Root layout wraps all pages */}
            <Route path="/" element={<Layout />}>
                {/* index route: rendered at exactly "/" */}
                <Route index element={<HomePage />} />

                {/* URL parameter: :postId is dynamic */}
                <Route path="posts/:postId" element={<PostDetailPage />} />
                <Route path="posts/by-slug/:slug" element={<PostDetailPage bySlug />} />

                {/* Auth routes */}
                <Route path="login"    element={<LoginPage />} />
                <Route path="register" element={<RegisterPage />} />

                {/* Dashboard */}
                <Route path="dashboard" element={<DashboardPage />} />

                {/* 404 — catch-all */}
                <Route path="*" element={<NotFoundPage />} />
            </Route>
        </Routes>
    );
}
Note: The parent <Route path="/" element={<Layout />}> renders the Layout component for all child routes. The Layout component renders <Outlet /> to mark where child route content appears — the equivalent of {"{{ children }}"} in other frameworks. If a nested route matches, its element renders inside the parent’s <Outlet />. The root <Route index> renders at the exact parent path (no extra path segment).
Tip: NavLink has a special className prop that receives a function with an isActive argument: className={({ isActive }) => isActive ? "font-bold text-blue-600" : "text-gray-600"}. Use NavLink for navigation bars where the active link should look different from inactive ones. Use plain Link for all other internal navigation (post titles, tag links, pagination buttons).
Warning: In React Router v6, routes are matched exactly by default — no trailing slash issues and no need for the exact prop that was required in v5. However, the route order inside <Routes> still matters for ambiguous paths. React Router v6 uses a specificity algorithm (more specific paths win), but for clarity, define specific paths before wildcard paths. Always put path="*" (404) last.

Layout with Outlet

// src/components/layout/Layout.jsx
import { Outlet, Link, NavLink } from "react-router-dom";
import { useCurrentUser } from "@/hooks/useCurrentUser";

export default function Layout() {
    const { user, isLoggedIn } = useCurrentUser();

    return (
        <div className="min-h-screen bg-gray-50">
            {/* Header — shown on all pages */}
            <header className="bg-white border-b border-gray-200 sticky top-0 z-10">
                <div className="max-w-4xl mx-auto px-4 h-16 flex items-center justify-between">
                    <Link to="/" className="text-xl font-bold text-gray-900">Blog</Link>
                    <nav className="flex items-center gap-4">
                        <NavLink
                            to="/"
                            end   {/* 'end' = only active at exact "/" */}
                            className={({ isActive }) =>
                                isActive ? "text-blue-600 font-medium" : "text-gray-600 hover:text-gray-900"
                            }
                        >
                            Home
                        </NavLink>
                        {isLoggedIn ? (
                            <>
                                <NavLink to="/dashboard" className={({ isActive }) =>
                                    isActive ? "text-blue-600 font-medium" : "text-gray-600"
                                }>
                                    Dashboard
                                </NavLink>
                                <span className="text-sm text-gray-500">{user.name}</span>
                            </>
                        ) : (
                            <>
                                <Link to="/login" className="text-gray-600 hover:text-gray-900">Login</Link>
                                <Link to="/register" className="bg-blue-600 text-white px-3 py-1 rounded">
                                    Sign Up
                                </Link>
                            </>
                        )}
                    </nav>
                </div>
            </header>

            {/* Page content — child routes render here */}
            <main className="max-w-4xl mx-auto px-4 py-8">
                <Outlet />
            </main>

            <footer className="border-t border-gray-200 py-8 text-center text-gray-400 text-sm">
                © 2025 Blog Application
            </footer>
        </div>
    );
}

URL Parameters and Search Params

import { useParams, useSearchParams } from "react-router-dom";

// ── URL parameters: /posts/:postId ────────────────────────────────────────────
function PostDetailPage() {
    const { postId } = useParams();   // e.g., "42" for /posts/42
    const { post, isLoading } = usePost(Number(postId));
    // ...
}

// ── Search parameters: /posts?page=2&tag=python ───────────────────────────────
function HomePage() {
    const [searchParams, setSearchParams] = useSearchParams();
    const page = Number(searchParams.get("page") ?? 1);
    const tag  = searchParams.get("tag") ?? null;

    function handlePageChange(newPage) {
        setSearchParams({ page: newPage, ...(tag ? { tag } : {}) });
    }

    function handleTagSelect(tagSlug) {
        setSearchParams(tagSlug ? { tag: tagSlug, page: 1 } : {});
    }
    // ...
}

Common Mistakes

❌ Wrong — “/” always matches, so home is always highlighted:

<NavLink to="/">Home</NavLink>   // active on "/", "/posts/42", "/login"!

✅ Correct — use end to match exactly “/”:

<NavLink to="/" end>Home</NavLink>   // ✓ only active at exactly "/"

Mistake 2 — Using React Router v5 patterns in v6

❌ Wrong — v5 API does not work in v6:

<Switch>  <Route exact path="/" component={HomePage} />  </Switch>

✅ Correct — v6 API:

<Routes>  <Route path="/" element={<HomePage />} />  </Routes>   // ✓

Quick Reference

Component / Hook Purpose Example
<Routes> Container for route definitions Wraps all <Route> elements
<Route> Maps path to component <Route path="posts/:id" element={<Detail />} />
<Outlet /> Where child routes render Inside Layout component
<Link to> Internal navigation link <Link to="/posts/42">Read</Link>
<NavLink to end> Link with active styling Navigation bar links
useParams() Read URL params const { postId } = useParams()
useSearchParams() Read/write query string searchParams.get("page")

🧠 Test Yourself

The URL is /posts/42?page=2. In the component for /posts/:postId, what do useParams() and useSearchParams() return?