Setting Up React Router — BrowserRouter, Routes and Route

Setting up React Router is the first concrete step in giving the MERN Blog real navigation. In this lesson you will install React Router v6, wrap the application in BrowserRouter, define the complete route map using Routes and Route, and verify that navigating between URLs renders the correct page component. By the end you will have a fully routed SPA with a consistent layout, a working 404 page, and all the route slots ready to receive real content in the chapters ahead.

Installation

cd client
npm install react-router-dom

Step 1 — Wrap the App in BrowserRouter

// src/main.jsx
import { StrictMode }    from 'react';
import { createRoot }    from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App               from './App.jsx';
import './index.css';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>
);
Note: BrowserRouter must wrap the entire application — typically in main.jsx, not in App.jsx. All React Router components and hooks (Link, Route, useNavigate, useParams) only work inside a component that is a descendant of BrowserRouter. If you try to use a React Router hook outside the BrowserRouter context, React throws an error: “useNavigate() may be used only in the context of a Router component.”
Tip: React Router v6 uses Routes (plural) as the container for route definitions, and the Route component takes an element prop (not a component prop as in v5). The element prop accepts JSX — element={<HomePage />} — not a reference to the component function. This means you pass props directly to the page component right there in the route definition, which is much more flexible than v5’s approach.
Warning: In React Router v6, the Routes component renders only the first matching route — there is no need for the exact prop that was required in v5. Route matching in v6 is always exact by default. The order of Route components inside Routes still matters for catch-alls: put the wildcard path="*" route last.

Step 2 — Define the Route Map in App.jsx

// src/App.jsx
import { Routes, Route } from 'react-router-dom';
import PageLayout   from '@/components/layout/PageLayout';
import HomePage     from '@/pages/HomePage';
import PostDetail   from '@/pages/PostDetailPage';
import LoginPage    from '@/pages/LoginPage';
import RegisterPage from '@/pages/RegisterPage';
import DashboardPage    from '@/pages/DashboardPage';
import CreatePostPage   from '@/pages/CreatePostPage';
import EditPostPage     from '@/pages/EditPostPage';
import NotFoundPage     from '@/pages/NotFoundPage';
import ProtectedRoute   from '@/components/auth/ProtectedRoute';

function App() {
  return (
    <PageLayout>
      <Routes>
        {/* Public routes */}
        <Route path="/"          element={<HomePage />} />
        <Route path="/posts/:id" element={<PostDetail />} />
        <Route path="/login"     element={<LoginPage />} />
        <Route path="/register"  element={<RegisterPage />} />

        {/* Protected routes — wrapped in ProtectedRoute */}
        <Route path="/dashboard"      element={<ProtectedRoute><DashboardPage /></ProtectedRoute>} />
        <Route path="/posts/new"      element={<ProtectedRoute><CreatePostPage /></ProtectedRoute>} />
        <Route path="/posts/:id/edit" element={<ProtectedRoute><EditPostPage /></ProtectedRoute>} />

        {/* 404 catch-all — must be last */}
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </PageLayout>
  );
}

export default App;

Step 3 — Create Page Stub Components

// src/pages/HomePage.jsx
function HomePage() {
  return (
    <div className="home-page">
      <h1>Latest Posts</h1>
      <p>Posts will appear here when connected to the API.</p>
    </div>
  );
}
export default HomePage;

// src/pages/LoginPage.jsx
function LoginPage() {
  return <div><h1>Login</h1><p>Login form goes here.</p></div>;
}
export default LoginPage;

// src/pages/RegisterPage.jsx
function RegisterPage() {
  return <div><h1>Register</h1><p>Register form goes here.</p></div>;
}
export default RegisterPage;

// src/pages/NotFoundPage.jsx
import { Link } from 'react-router-dom';
function NotFoundPage() {
  return (
    <div className="not-found">
      <h1>404 — Page Not Found</h1>
      <p>The page you are looking for does not exist.</p>
      <Link to="/">← Back to Home</Link>
    </div>
  );
}
export default NotFoundPage;

Nested Routes with Layout — Outlet Pattern

// Alternative: layout routes with Outlet
// Useful when some routes share a sub-layout (e.g. dashboard has its own sidebar)

// src/pages/DashboardLayout.jsx
import { Outlet } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside className="dashboard__sidebar">
        <nav>
          <Link to="/dashboard">My Posts</Link>
          <Link to="/dashboard/settings">Settings</Link>
        </nav>
      </aside>
      <main className="dashboard__content">
        <Outlet /> {/* matched child route renders here */}
      </main>
    </div>
  );
}

// App.jsx — nested routes
<Route path="/dashboard" element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
  <Route index          element={<MyPostsPage />} />  {/* /dashboard */}
  <Route path="settings" element={<SettingsPage />} /> {/* /dashboard/settings */}
</Route>

Common Mistakes

Mistake 1 — Defining routes in the wrong order

❌ Wrong — catch-all before specific routes:

<Routes>
  <Route path="*"          element={<NotFound />} />  {/* too early! */}
  <Route path="/posts/:id" element={<PostDetail />} /> {/* never reached */}
</Routes>

✅ Correct — wildcard always last:

<Routes>
  <Route path="/posts/:id" element={<PostDetail />} />
  <Route path="*"          element={<NotFound />} />   {/* ✓ last */}
</Routes>

Mistake 2 — Not wrapping App in BrowserRouter

❌ Wrong — using React Router components without a provider:

// main.jsx — BrowserRouter missing
createRoot(document.getElementById('root')).render(<App />);
// Error: useNavigate() may be used only in the context of a Router component

✅ Correct — BrowserRouter wraps the app in main.jsx.

Mistake 3 — Using the v5 component prop instead of v6 element prop

❌ Wrong — v5 API in a v6 project:

<Route path="/" component={HomePage} />  // v5 — does not work in v6

✅ Correct — v6 uses element with JSX:

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

Quick Reference

Task v6 Code
Install npm install react-router-dom
Wrap app <BrowserRouter><App /></BrowserRouter> in main.jsx
Route container <Routes>...</Routes>
Define a route <Route path="/path" element={<Page />} />
Index route <Route index element={<Home />} />
Catch-all 404 <Route path="*" element={<NotFound />} />
Nested layout Parent Route with child Routes and <Outlet />

🧠 Test Yourself

You define routes in this order: path="*", then path="/posts/:id". A user navigates to /posts/123. Which component renders?