JavaScript and React course Β· Module 6: React Router

Complete Routing Architecture - Navigation Command Center

5 min read
In this lesson9

Navigator, we've reached the final stage of our journey through the Asteroid Belt! It's time to gather all the navigation techniques we've learned and build a complete routing system - a true Navigation Command Center for our cosmic fleet.

In this lesson, we'll see how all React Router elements work together in one cohesive application.

Architecture of a Complete Application with Routing

When building an application with full routing, we need to plan several layers:

  1. Authentication layer - AuthContext + ProtectedRoute
  2. Navigation layer - NavLink with active styles
  3. Routes layer - nested Routes with Outlet
  4. Parameters layer - dynamic paths with useParams
  5. Redirects layer - 404 handling and security

Folder Structure

In larger applications, it's worth organizing routing-related files in a logical structure:

1src/
2β”œβ”€β”€ App.jsx              // Main component with BrowserRouter
3β”œβ”€β”€ routes/
4β”‚   β”œβ”€β”€ AppRoutes.jsx    // Definition of all routes
5β”‚   └── ProtectedRoute.jsx
6β”œβ”€β”€ context/
7β”‚   └── AuthContext.jsx  // Authentication context
8β”œβ”€β”€ layouts/
9β”‚   β”œβ”€β”€ MainLayout.jsx   // Layout with navigation and Outlet
10β”‚   └── DashboardLayout.jsx
11β”œβ”€β”€ pages/
12β”‚   β”œβ”€β”€ HomePage.jsx
13β”‚   β”œβ”€β”€ LoginPage.jsx
14β”‚   β”œβ”€β”€ MissionDetail.jsx
15β”‚   └── NotFound.jsx
16└── components/
17    └── Navigation.jsx   // NavLink menu

Such organization allows easy management of even extensive applications with dozens of routes.

Connecting AuthContext with Routing

The central element is the AuthProvider wrapping the entire application:

1function AuthProvider({ children }) {
2  const [user, setUser] = useState(null);
3
4  const login = (name) => setUser({ name });
5  const logout = () => setUser(null);
6
7  return (
8    <AuthContext.Provider value={{ user, login, logout }}>
9      {children}
10    </AuthContext.Provider>
11  );
12}
13
14// In App.jsx:
15function App() {
16  return (
17    <AuthProvider>
18      <BrowserRouter>
19        <Navigation />
20        <Routes>
21          {/* public and protected routes */}
22        </Routes>
23      </BrowserRouter>
24    </AuthProvider>
25  );
26}

The AuthProvider must be outside BrowserRouter so that the authentication context is available in all route components.

The navigation component combines NavLink (with active styles) with AuthContext (conditional display of options):

1function Navigation() {
2  const { user, logout } = useAuth();
3
4  return (
5    <nav>
6      <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>
7        Home
8      </NavLink>
9      <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>
10        Missions
11      </NavLink>
12
13      {user ? (
14        <>
15          <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>
16            Dashboard
17          </NavLink>
18          <span>Logged in: {user.name}</span>
19          <button onClick={logout}>Logout</button>
20        </>
21      ) : (
22        <NavLink to="/login">Login</NavLink>
23      )}
24    </nav>
25  );
26}

This way the navigation automatically responds to the authentication state - a logged-in user sees the Dashboard and logout button, while an unauthenticated user sees the login link.

Nested Routes with Layout

The missions section uses nested routes with Outlet:

1function MissionsLayout() {
2  return (
3    <div className="missions-layout">
4      <aside>
5        <h3>Missions</h3>
6        <NavLink to="/missions/apollo">Apollo</NavLink>
7        <NavLink to="/missions/artemis">Artemis</NavLink>
8        <NavLink to="/missions/voyager">Voyager</NavLink>
9      </aside>
10      <main>
11        <Outlet />
12      </main>
13    </div>
14  );
15}
16
17// In route configuration:
18<Route path="/missions" element={<MissionsLayout />}>
19  <Route index element={<MissionsList />} />
20  <Route path=":missionId" element={<MissionDetail />} />
21</Route>

The index route (<Route index />) renders the default content (missions list) when the user is at /missions. The dynamic parameter :missionId handles specific mission details.

Complete Route Configuration

Here's what the full route configuration looks like combining all the techniques we've learned:

1function App() {
2  return (
3    <AuthProvider>
4      <BrowserRouter>
5        <Navigation />
6        <Routes>
7          {/* Home page */}
8          <Route path="/" element={<HomePage />} />
9
10          {/* Login */}
11          <Route path="/login" element={<LoginPage />} />
12
13          {/* Nested routes with layout */}
14          <Route path="/missions" element={<MissionsLayout />}>
15            <Route index element={<MissionsList />} />
16            <Route path=":missionId" element={<MissionDetail />} />
17          </Route>
18
19          {/* Protected route */}
20          <Route path="/dashboard" element={
21            <ProtectedRoute>
22              <DashboardPage />
23            </ProtectedRoute>
24          } />
25
26          {/* 404 handling */}
27          <Route path="*" element={<NotFound />} />
28        </Routes>
29      </BrowserRouter>
30    </AuthProvider>
31  );
32}

The order of elements matters:

  • Specific paths (/, /login, /missions) are matched first
  • The path="*" route must be last - it catches everything that doesn't match any defined route

Login with Path Remembering

The login page uses useNavigate and useLocation to redirect the user back to the page they were redirected from:

1function LoginPage() {
2  const [name, setName] = useState('');
3  const { login } = useAuth();
4  const navigate = useNavigate();
5  const location = useLocation();
6
7  // Path the user was redirected from
8  const from = location.state?.from?.pathname || '/dashboard';
9
10  const handleSubmit = (e) => {
11    e.preventDefault();
12    if (name.trim()) {
13      login(name);
14      navigate(from, { replace: true }); // replace: true removes /login from history
15    }
16  };
17
18  return (
19    <form onSubmit={handleSubmit}>
20      <input value={name} onChange={(e) => setName(e.target.value)} />
21      <button type="submit">Log In</button>
22    </form>
23  );
24}

The replace: true option in navigate is crucial - thanks to it, the user won't return to the login page after clicking "Back" in the browser.

404 Page Handling

The Not Found page contains a link back to the home page:

1function NotFound() {
2  return (
3    <div className="not-found">
4      <h1>404</h1>
5      <p>Page not found</p>
6      <Link to="/">Return to home page</Link>
7    </div>
8  );
9}

Architecture Summary

A complete application with routing in React combines the following elements:

ElementRoleHook / Component
Route containerEnables routingBrowserRouter
Route definitionMaps URLs to componentsRoutes, Route
NavigationLinks with active stylesNavLink
NestingShared layoutOutlet, Route children
Dynamic parametersElement detailsuseParams
Programmatic navigationRedirect after actionuseNavigate
Location statePassing hidden datauseLocation
Route protectionEnforcing loginProtectedRoute + Navigate
Error handlingNon-existent pagesRoute path="*"
Auth contextGlobal user datacreateContext + useContext

Try the complete application in the editor below - log in, navigate between missions, check the Dashboard, and test the 404 page!

Code for this lesson: App.jsx
1import React, { useState, createContext, useContext, Suspense, lazy } from 'react';
2import {
3  BrowserRouter, Routes, Route, Link, NavLink,
4  useParams, useNavigate, useLocation, Navigate, Outlet
5} from 'react-router-dom';
6
7// Auth Context
8const AuthContext = createContext();
9function useAuth() { return useContext(AuthContext); }
10
11function AuthProvider({ children }) {
12  const [user, setUser] = useState(null);
13  const login = (name) => setUser({ name });
14  const logout = () => setUser(null);
15  return (
16    <AuthContext.Provider value={{ user, login, logout }}>
17      {children}
18    </AuthContext.Provider>
19  );
20}
21
22// Protected Route
23function ProtectedRoute({ children }) {
24  const { user } = useAuth();
25  const location = useLocation();
26  if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
27  return children;
28}
29
30// Components
31function Navigation() {
32  const { user, logout } = useAuth();
33  return (
34    <nav>
35      <div className="nav-brand">SpaceApp</div>
36      <div className="nav-links">
37        <NavLink to="/" className={({isActive}) => isActive ? 'active' : ''}>Home</NavLink>
38        <NavLink to="/missions" className={({isActive}) => isActive ? 'active' : ''}>Missions</NavLink>
39        {user ? (
40          <>
41            <NavLink to="/dashboard" className={({isActive}) => isActive ? 'active' : ''}>Dashboard</NavLink>
42            <span className="user-badge">{user.name}</span>
43            <button onClick={logout}>Logout</button>
44          </>
45        ) : (
46          <NavLink to="/login" className={({isActive}) => isActive ? 'active' : ''}>Login</NavLink>
47        )}
48      </div>
49    </nav>
50  );
51}
52
53function HomePage() {
54  return (
55    <div className="page home">
56      <h1>Welcome to SpaceApp</h1>
57      <p>Your gateway to space exploration</p>
58      <div className="features">
59        <div className="feature">Track Missions</div>
60        <div className="feature">View Analytics</div>
61        <div className="feature">Manage Crew</div>
62      </div>
63    </div>
64  );
65}
66
67function LoginPage() {
68  const [name, setName] = useState('');
69  const { login } = useAuth();
70  const navigate = useNavigate();
71  const location = useLocation();
72  const from = location.state?.from?.pathname || '/dashboard';
73
74  const handleSubmit = (e) => {
75    e.preventDefault();
76    if (name.trim()) {
77      login(name);
78      navigate(from, { replace: true });
79    }
80  };
81
82  return (
83    <div className="page login">
84      <h1>Login</h1>
85      <form onSubmit={handleSubmit}>
86        <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Username" />
87        <button type="submit">Login</button>
88      </form>
89    </div>
90  );
91}
92
93function MissionsLayout() {
94  return (
95    <div className="missions-layout">
96      <aside>
97        <h3>Missions</h3>
98        <NavLink to="/missions/apollo">Apollo</NavLink>
99        <NavLink to="/missions/artemis">Artemis</NavLink>
100        <NavLink to="/missions/voyager">Voyager</NavLink>
101      </aside>
102      <main><Outlet /></main>
103    </div>
104  );
105}
106
107function MissionsList() {
108  return <div className="content"><h2>Select a mission</h2></div>;
109}
110
111function MissionDetail() {
112  const { missionId } = useParams();
113  return (
114    <div className="content">
115      <h2>Mission: {missionId?.toUpperCase()}</h2>
116      <p>Details for {missionId} mission</p>
117    </div>
118  );
119}
120
121function DashboardPage() {
122  const { user } = useAuth();
123  return (
124    <div className="page dashboard">
125      <h1>Dashboard</h1>
126      <p>Welcome back, {user?.name}!</p>
127      <div className="stats">
128        <div className="stat"><span>Missions</span>12</div>
129        <div className="stat"><span>Crew</span>48</div>
130        <div className="stat"><span>Ships</span>8</div>
131      </div>
132    </div>
133  );
134}
135
136function NotFound() {
137  return (
138    <div className="page not-found">
139      <h1>404</h1>
140      <p>Page not found</p>
141      <Link to="/">Go Home</Link>
142    </div>
143  );
144}
145
146function App() {
147  return (
148    <AuthProvider>
149      <BrowserRouter>
150        <div className="app">
151          <Navigation />
152          <Routes>
153            <Route path="/" element={<HomePage />} />
154            <Route path="/login" element={<LoginPage />} />
155            <Route path="/missions" element={<MissionsLayout />}>
156              <Route index element={<MissionsList />} />
157              <Route path=":missionId" element={<MissionDetail />} />
158            </Route>
159            <Route path="/dashboard" element={
160              <ProtectedRoute><DashboardPage /></ProtectedRoute>
161            } />
162            <Route path="*" element={<NotFound />} />
163          </Routes>
164        </div>
165      </BrowserRouter>
166    </AuthProvider>
167  );
168}
169
170export default App;

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. How do you implement a Protected Route in React Router?

  2. 2. Where in the parent component do you place <Outlet /> to render nested routes?

These are 2 of 7 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Implement 404 error handling with Route path='*' and a NotFound component. Add navigation with links to existing and non-existent pages to test the error handling.

  • Click in order

    Arrange the programmatic navigation with state:

  • Vertical ordering

    Arrange the steps for programmatic navigation using useNavigate:

  • Vertical ordering

    Arrange the steps for passing and reading data between routes:

  • Vertical ordering

    Arrange the steps for implementing a Protected Route with the Navigate component:

  • Click in order

    Arrange the search params management:

  • Vertical ordering

    Arrange the navigation steps in React Router from clicking a link to displaying the page:

  • Vertical ordering

    Arrange the steps for handling unauthorized access to a protected route:

  • Horizontal ordering

    Arrange the elements of a route handling the 404 page:

  • Horizontal ordering

    Arrange the Route syntax with a dynamic id parameter:

  • Code editor

    Build a comprehensive Navigation System including: React Router setup with BrowserRouter, nested routes for the /fleet/:id/missions/:missionId structure, dynamic routing with params, Link and NavLink components with active styles, useNavigate for programmatic navigation, useParams and useLocation hooks, protected routes with authentication, lazy loading with React.lazy and Suspense, search params with useSearchParams, and 404 error handling. Create a navigation system worthy of the most advanced fleet traversing the Asteroid Belt!

Useful articles