Code To Learn logo

Code To Learn

M4: Routes & Navigation

L10: Creating a NotFoundPage

Build a 404 error page for handling invalid routes

What happens when someone visits a URL that doesn't exist? Right now: a blank page. Let's create a friendly 404 error page to guide users back!

What You'll Learn

  • Create a 404 Not Found page
  • Use catch-all routes with *
  • Understand route matching order
  • Build helpful error pages
  • Guide users back to valid pages

Why We Need a 404 Page

Bad user experience:

User visits: /invalid-page
Result: Blank screen, no guidance
User thinks: "Is the site broken?"

Good user experience:

User visits: /invalid-page
Result: Friendly 404 page with navigation
User thinks: "Okay, let me go back home"

Step 1: Create NotFoundPage Component

Create the Page File

Terminal
touch src/pages/NotFoundPage.jsx

Build the 404 UI

Create a friendly error page:

src/pages/NotFoundPage.jsx
import { Link } from 'react-router-dom';

export default function NotFoundPage() {
  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-50">
      <div className="text-center px-4">
        {/* Large 404 */}
        <h1 className="text-9xl font-bold text-gray-300 mb-4">
          404
        </h1>

        {/* Error Message */}
        <h2 className="text-3xl font-bold text-gray-900 mb-2">
          Page Not Found
        </h2>

        <p className="text-gray-600 mb-8 max-w-md mx-auto">
          Oops! The page you're looking for doesn't exist. It might have been moved or deleted.
        </p>

        {/* Navigation Buttons */}
        <div className="flex gap-4 justify-center">
          <Link
            to="/"
            className="bg-pink-600 hover:bg-pink-700 text-white font-semibold py-3 px-6 rounded-lg transition-colors"
          >
            Go Home
          </Link>

          <button
            onClick={() => window.history.back()}
            className="bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-3 px-6 rounded-lg transition-colors"
          >
            Go Back
          </button>
        </div>

        {/* Optional: Helpful Links */}
        <div className="mt-12">
          <p className="text-sm text-gray-600 mb-4">Or try these pages:</p>
          <div className="flex gap-4 justify-center text-sm">
            <Link to="/" className="text-pink-600 hover:underline">
              Home
            </Link>
            <Link to="/about" className="text-pink-600 hover:underline">
              About
            </Link>
            <Link to="/contact" className="text-pink-600 hover:underline">
              Contact
            </Link>
          </div>
        </div>
      </div>
    </div>
  );
}

Features:

  • Large "404" heading
  • Friendly error message
  • "Go Home" button
  • "Go Back" button (browser history)
  • Helpful navigation links
  • Clean, professional design

Step 2: Add Catch-All Route

Now add the route to handle all unmatched URLs:

Update Router Configuration

Add the catch-all route to Router.jsx:

src/components/Router.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from '../pages/HomePage';
import ListingDetailsPage from '../pages/ListingDetailsPage';
import NotFoundPage from '../pages/NotFoundPage'; 

export default function Router() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/listing/:id" element={<ListingDetailsPage />} />
        <Route path="*" element={<NotFoundPage />} /> {/* [!code ++] */
      </Routes>
    </BrowserRouter>
  );
}

The * path:

  • Matches any URL
  • Acts as a catch-all
  • Must be last in route list

Test the 404 Page

Try visiting invalid URLs:

http://localhost:5173/invalid-page       → 404 Page
http://localhost:5173/doesnt-exist      → 404 Page
http://localhost:5173/listing/abc/xyz   → 404 Page
http://localhost:5173/random            → 404 Page

All should show your NotFoundPage!

Understanding the Catch-All Route

The * path is special:

The * wildcard:

<Route path="*" element={<NotFoundPage />} />

Matches:

  • /anything
  • /multiple/segments/here
  • /listing/invalid/extra/parts
  • Any URL not matched by previous routes

Why it works:

  • React Router checks routes in order
  • First match wins
  • * matches everything
  • So if no other route matched, * will

Order matters! Most specific first:

<Routes>
  {/* 1. Exact paths */}
  <Route path="/" element={<HomePage />} />

  {/* 2. Specific static paths */}
  <Route path="/about" element={<AboutPage />} />

  {/* 3. Dynamic routes */}
  <Route path="/listing/:id" element={<DetailsPage />} />

  {/* 4. Catch-all LAST */}
  <Route path="*" element={<NotFoundPage />} />
</Routes>

If you put * first:

<Routes>
  <Route path="*" element={<NotFoundPage />} /> {/* ❌ WRONG */}
  <Route path="/" element={<HomePage />} />
  <Route path="/listing/:id" element={<DetailsPage />} />
</Routes>

Everything shows 404! The * catches all URLs before other routes can match.

Route matching with catch-all:

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/listing/:id" element={<Details />} />
  <Route path="*" element={<NotFound />} />
</Routes>

URL → Route matching:

/                    → HomePage (exact match)
/listing/42          → DetailsPage (dynamic match)
/listing/999         → DetailsPage (dynamic match)
/about               → NotFoundPage (no match, caught by *)
/listing/42/invalid  → NotFoundPage (no match, caught by *)
/anything            → NotFoundPage (no match, caught by *)

Creative 404 Page Ideas

Make your 404 page memorable:

Using Browser History

The "Go Back" button uses browser history:

<button onClick={() => window.history.back()}>
  Go Back
</button>

How it works:

User journey:
1. User on /
2. Clicks link to /listing/42
3. Manually types /invalid-page
4. Clicks "Go Back"
5. Returns to /listing/42

Alternative with useNavigate:

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

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

  return (
    <button onClick={() => navigate(-1)}>
      Go Back
    </button>
  );
}

Both work the same way!

Advanced: Custom 404 for Specific Routes

You can have multiple 404 pages:

<Routes>
  {/* Main routes */}
  <Route path="/" element={<HomePage />} />
  
  {/* Listings section with own 404 */}
  <Route path="/listing">
    <Route index element={<ListingsPage />} />
    <Route path=":id" element={<DetailsPage />} />
    <Route path="*" element={<ListingNotFound />} />
  </Route>

  {/* Profile section with own 404 */}
  <Route path="/profile">
    <Route index element={<ProfilePage />} />
    <Route path="settings" element={<SettingsPage />} />
    <Route path="*" element={<ProfileNotFound />} />
  </Route>

  {/* Global 404 */}
  <Route path="*" element={<GlobalNotFound />} />
</Routes>

Different 404s for different sections!

404 Page Best Practices

404 Page Complete!

Your app now gracefully handles invalid routes with a friendly 404 page. Users will always have a way to navigate back to safety!

Quick Recap

What we accomplished:

  • ✅ Created NotFoundPage component
  • ✅ Added catch-all route with *
  • ✅ Provided navigation options
  • ✅ Understood route matching order
  • ✅ Built user-friendly error page

Key concepts:

  • Catch-all route - path="*" matches everything
  • Route order - More specific routes before catch-all
  • User guidance - Provide clear navigation options
  • Browser history - window.history.back() or navigate(-1)
  • Professional UX - Handle errors gracefully

What's Next?

In Lesson 11, we'll learn about programmatic navigation using the useNavigate hook. This lets you navigate from JavaScript code (like after form submissions or button clicks)! 🚀