On our cosmic journey through the world of React Router, it's time to delve into the concept of nested routes. Just as in real outer space, where we have galaxies containing solar systems, which in turn contain planets with their moons - in React applications we can create hierarchical navigation structures.
Nested routes are a technique for organizing routing in an application where some routes (components) contain other routes (components) within themselves. This allows for creating more intuitive, hierarchical navigation structures that reflect the logical layout of the application.
Nested routes are particularly useful in larger applications where there are many subsections with their own views.
In React Router v6, nested routes are implemented by placing
Route components inside other Route components and using the Outlet component to specify where sub-route components should be rendered.1import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';
2
3function App() {
4 return (
5 <BrowserRouter>
6 <Routes>
7 <Route path="/" element={<Layout />}>
8 <Route index element={<Home />} />
9 <Route path="galaxies" element={<GalaxiesList />} />
10 <Route path="galaxies/:galaxyId" element={<GalaxyLayout />}>
11 <Route index element={<GalaxyOverview />} />
12 <Route path="systems" element={<SolarSystemsList />} />
13 <Route path="systems/:systemId" element={<SolarSystemDetail />} />
14 </Route>
15 </Route>
16 </Routes>
17 </BrowserRouter>
18 );
19}In the example above:
Layout is the main component that will contain all nested viewsGalaxyLayout is the component that will serve as a "container" for galaxy detailsindex attribute specifies what should be rendered when the URL matches exactly the parent pathThe key element of nested routes is the
Outlet component, which acts as a "placeholder" for child components. It specifies where in the parent component the child route components should be rendered:1import { Outlet, Link } from 'react-router-dom';
2
3function Layout() {
4 return (
5 <div className="app-layout">
6 <header>
7 <h1>Cosmic Atlas</h1>
8 <nav>
9 <Link to="/">Home</Link>
10 <Link to="/galaxies">Galaxies</Link>
11 </nav>
12 </header>
13
14 <main>
15 {/* Sub-route components will be rendered here */}
16 <Outlet />
17 </main>
18
19 <footer>© 2150 Intergalactic Navigation Center</footer>
20 </div>
21 );
22}
23
24function GalaxyLayout() {
25 const { galaxyId } = useParams();
26
27 return (
28 <div className="galaxy-layout">
29 <h2>Galaxy: {galaxyId}</h2>
30 <nav className="galaxy-navigation">
31 <Link to={`/galaxies/${galaxyId}`}>Overview</Link>
32 <Link to={`/galaxies/${galaxyId}/systems`}>Solar Systems</Link>
33 </nav>
34
35 <div className="galaxy-content">
36 {/* Galaxy sub-route components will be rendered here */}
37 <Outlet />
38 </div>
39 </div>
40 );
41}React Router v6 introduced several useful features for better handling of nested routes:
An index route specifies what should be rendered when the URL matches exactly the parent path:
1<Route path="galaxies" element={<GalaxiesLayout />}>
2 {/* This will be rendered for /galaxies */}
3 <Route index element={<GalaxiesList />} />
4
5 {/* This will be rendered for /galaxies/:galaxyId */}
6 <Route path=":galaxyId" element={<GalaxyDetails />} />
7</Route>In nested components, we can use relative links that are resolved relative to the current path:
1import { Link, useParams } from 'react-router-dom';
2
3function GalaxySystemsList() {
4 const { galaxyId } = useParams();
5
6 return (
7 <div>
8 <h3>Solar systems in galaxy {galaxyId}</h3>
9 <ul>
10 {systems.map(system => (
11 <li key={system.id}>
12 {/* Relative link - will be resolved to /galaxies/:galaxyId/systems/:systemId */}
13 <Link to={system.id}>{system.name}</Link>
14 </li>
15 ))}
16 </ul>
17
18 {/* Relative link "up" - go back one level */}
19 <Link to="..">Return to galaxy overview</Link>
20 </div>
21 );
22}One of the challenges when working with nested routes is passing data between different nesting levels. React Router v6 offers several solutions:
The
useOutletContext hook allows passing data from a parent component to a child:1import { Outlet, useOutletContext, useParams } from 'react-router-dom';
2
3function GalaxyLayout() {
4 const { galaxyId } = useParams();
5 const [galaxy, setGalaxy] = useState(null);
6
7 useEffect(() => {
8 // Fetch galaxy data
9 fetchGalaxy(galaxyId).then(data => setGalaxy(data));
10 }, [galaxyId]);
11
12 // Pass galaxy data to child components
13 return (
14 <div className="galaxy-layout">
15 <h2>{galaxy?.name || 'Loading...'}</h2>
16 <Outlet context={{ galaxy, updateGalaxy: setGalaxy }} />
17 </div>
18 );
19}
20
21function GalaxyOverview() {
22 // Receive context from the parent component
23 const { galaxy, updateGalaxy } = useOutletContext();
24
25 if (!galaxy) return <div>Loading galaxy data...</div>;
26
27 return (
28 <div className="galaxy-overview">
29 <h3>Overview of galaxy {galaxy.name}</h3>
30 <p>Type: {galaxy.type}</p>
31 <p>Number of systems: {galaxy.systemsCount}</p>
32 {/* Rest of the component */}
33 </div>
34 );
35}For more complex scenarios, React Context can be used to share state between components:
1import { createContext, useContext, useState } from 'react';
2import { Outlet } from 'react-router-dom';
3
4// Create context
5const GalaxyContext = createContext();
6
7// Hook for easy context access
8function useGalaxy() {
9 return useContext(GalaxyContext);
10}
11
12function GalaxyProvider({ children }) {
13 const [selectedGalaxy, setSelectedGalaxy] = useState(null);
14 const [favoritesSystems, setFavoriteSystems] = useState([]);
15
16 const addFavoriteSystem = (system) => {
17 setFavoriteSystems(prev => [...prev, system]);
18 };
19
20 // Context value
21 const value = {
22 selectedGalaxy,
23 setSelectedGalaxy,
24 favoritesSystems,
25 addFavoriteSystem
26 };
27
28 return (
29 <GalaxyContext.Provider value={value}>
30 {children}
31 </GalaxyContext.Provider>
32 );
33}
34
35function GalaxiesRoot() {
36 return (
37 <GalaxyProvider>
38 <div className="galaxies-section">
39 <h2>Galaxy Exploration</h2>
40 <Outlet />
41 </div>
42 </GalaxyProvider>
43 );
44}
45
46// Usage in a child component
47function SolarSystemDetail() {
48 const { favoritesSystems, addFavoriteSystem } = useGalaxy();
49 const { systemId } = useParams();
50
51 // Rest of the component
52}Nested routes can be used to implement various navigation patterns:
1<Routes>
2 <Route path="/" element={<MainLayout />}>
3 <Route element={<AuthLayout />}>
4 <Route path="dashboard" element={<Dashboard />} />
5 <Route path="profile" element={<Profile />} />
6 <Route path="settings" element={<SettingsLayout />}>
7 <Route path="account" element={<AccountSettings />} />
8 <Route path="notifications" element={<NotificationSettings />} />
9 </Route>
10 </Route>
11 <Route path="about" element={<About />} />
12 <Route path="contact" element={<Contact />} />
13 </Route>
14</Routes>In this example:
MainLayout contains the general page structureAuthLayout handles authentication for protected pathsSettingsLayout creates an additional navigation level for settings1<Routes>
2 <Route path="galaxies" element={<GalaxiesList />} />
3 <Route path="galaxies/:galaxyId" element={<GalaxyLayout />}>
4 <Route index element={<GalaxyOverview />} />
5 <Route path="systems" element={<SystemsList />} />
6 <Route path="systems/:systemId" element={<SystemLayout />}>
7 <Route index element={<SystemOverview />} />
8 <Route path="planets" element={<PlanetsList />} />
9 <Route path="planets/:planetId" element={<PlanetDetails />} />
10 </Route>
11 </Route>
12</Routes>This structure creates deep nesting of routes that reflects the hierarchy galaxy > solar system > planet.
React Router v6 introduced the concept of named outlets, which allows rendering multiple child components in different places:
1function DashboardLayout() {
2 return (
3 <div className="dashboard">
4 <aside className="sidebar">
5 {/* Named outlet for sidebar navigation */}
6 <Outlet name="sidebar" />
7 </aside>
8 <div className="main-content">
9 {/* Default outlet for main content */}
10 <Outlet />
11 </div>
12 <aside className="auxiliary-panel">
13 {/* Named outlet for auxiliary panel */}
14 <Outlet name="auxiliary" />
15 </aside>
16 </div>
17 );
18}When working with nested routes, it's worth keeping a few optimization techniques in mind:
For improved performance, components can be loaded dynamically only when they are needed:
1import { Suspense, lazy } from 'react';
2import { Routes, Route } from 'react-router-dom';
3
4// Lazy loading components
5const GalaxyLayout = lazy(() => import('./GalaxyLayout'));
6const GalaxiesList = lazy(() => import('./GalaxiesList'));
7const SolarSystemDetails = lazy(() => import('./SolarSystemDetails'));
8
9function App() {
10 return (
11 <Routes>
12 <Route path="/" element={<MainLayout />}>
13 {/* Lazily loaded component */}
14 <Route
15 path="galaxies"
16 element={
17 <Suspense fallback={<div>Loading galaxies...</div>}>
18 <GalaxiesList />
19 </Suspense>
20 }
21 />
22 <Route
23 path="galaxies/:galaxyId/*"
24 element={
25 <Suspense fallback={<div>Loading galaxy data...</div>}>
26 <GalaxyLayout />
27 </Suspense>
28 }
29 />
30 </Route>
31 </Routes>
32 );
33}Render optimization can be especially important with deep nesting, when a URL parameter change at a deep level can cause unnecessary re-rendering of higher-level components:
1import { memo } from 'react';
2import { Outlet, useParams } from 'react-router-dom';
3
4// Optimized component - won't re-render if its props don't change
5const MemoizedGalaxyLayout = memo(function GalaxyLayout() {
6 const { galaxyId } = useParams();
7
8 console.log(`Rendering GalaxyLayout for galaxy ${galaxyId}`);
9
10 return (
11 <div className="galaxy-layout">
12 <h2>Galaxy: {galaxyId}</h2>
13 <Outlet />
14 </div>
15 );
16});Below is an example of a complete space application with nested routes:
1import React from 'react';
2import {
3 BrowserRouter,
4 Routes,
5 Route,
6 Outlet,
7 Link,
8 NavLink,
9 useParams,
10 useOutletContext,
11 useNavigate
12} from 'react-router-dom';
13
14// Main application layout
15function AppLayout() {
16 return (
17 <div className="cosmos-explorer">
18 <header className="main-header">
19 <h1>Cosmic Traveler's Atlas</h1>
20 <MainNavigation />
21 </header>
22
23 <main className="main-content">
24 <Outlet />
25 </main>
26
27 <footer className="main-footer">
28 <p>© 2150 Intergalactic Travel Agency</p>
29 </footer>
30 </div>
31 );
32}
33
34// Main navigation
35function MainNavigation() {
36 return (
37 <nav className="main-nav">
38 <ul>
39 <li><NavLink to="/" end>Command Center</NavLink></li>
40 <li><NavLink to="/galaxies">Galaxies</NavLink></li>
41 <li><NavLink to="/missions">Missions</NavLink></li>
42 <li><NavLink to="/crew">Crew</NavLink></li>
43 </ul>
44 </nav>
45 );
46}
47
48// Home page
49function Dashboard() {
50 return (
51 <div className="dashboard">
52 <h2>Spaceship Command Center</h2>
53 <p>Welcome, Captain! Which location would you like to explore today?</p>
54
55 <div className="dashboard-tiles">
56 <div className="tile">
57 <h3>Galaxies</h3>
58 <p>Explore different galaxies in the universe</p>
59 <Link to="/galaxies" className="tile-link">Go to galaxies</Link>
60 </div>
61
62 <div className="tile">
63 <h3>Missions</h3>
64 <p>Browse current and future space missions</p>
65 <Link to="/missions" className="tile-link">Go to missions</Link>
66 </div>
67
68 <div className="tile">
69 <h3>Crew</h3>
70 <p>Manage spaceship crew members</p>
71 <Link to="/crew" className="tile-link">Go to crew</Link>
72 </div>
73 </div>
74 </div>
75 );
76}
77
78// ===== GALAXIES SECTION =====
79
80// Galaxies layout
81function GalaxiesLayout() {
82 return (
83 <div className="galaxies-section">
84 <h2>Galaxy Exploration</h2>
85 <Outlet />
86 </div>
87 );
88}
89
90// Galaxies list
91function GalaxiesList() {
92 const galaxies = [
93 { id: 'milky-way', name: 'Milky Way', type: 'Spiral' },
94 { id: 'andromeda', name: 'Andromeda', type: 'Spiral' },
95 { id: 'triangulum', name: 'Triangulum Galaxy', type: 'Spiral' },
96 { id: 'large-magellanic', name: 'Large Magellanic Cloud', type: 'Irregular' }
97 ];
98
99 return (
100 <div className="galaxies-list">
101 <h3>Available Galaxies</h3>
102 <div className="galaxy-cards">
103 {galaxies.map(galaxy => (
104 <div key={galaxy.id} className="galaxy-card">
105 <h4>{galaxy.name}</h4>
106 <p>Type: {galaxy.type}</p>
107 <Link to={galaxy.id} className="galaxy-link">
108 Explore galaxy
109 </Link>
110 </div>
111 ))}
112 </div>
113 </div>
114 );
115}
116
117// Galaxy details layout
118function GalaxyLayout() {
119 const { galaxyId } = useParams();
120 const navigate = useNavigate();
121
122 // Galaxy data simulation (in a real application fetched from API)
123 const galaxyData = {
124 'milky-way': {
125 name: 'Milky Way',
126 type: 'Spiral',
127 diameter: '100,000 light-years',
128 stars: '200-400 billion',
129 systems: [
130 { id: 'solar', name: 'Solar System' },
131 { id: 'alpha-centauri', name: 'Alpha Centauri' },
132 { id: 'sirius', name: 'Sirius' }
133 ]
134 },
135 'andromeda': {
136 name: 'Andromeda',
137 type: 'Spiral',
138 diameter: '220,000 light-years',
139 stars: '1 trillion',
140 systems: [
141 { id: 'system-1', name: 'System M31-A' },
142 { id: 'system-2', name: 'System M31-B' }
143 ]
144 },
145 'triangulum': {
146 name: 'Triangulum Galaxy',
147 type: 'Spiral',
148 diameter: '60,000 light-years',
149 stars: '40 billion',
150 systems: [
151 { id: 'system-1', name: 'System T1' }
152 ]
153 },
154 'large-magellanic': {
155 name: 'Large Magellanic Cloud',
156 type: 'Irregular',
157 diameter: '14,000 light-years',
158 stars: '30 billion',
159 systems: []
160 }
161 };
162
163 const galaxy = galaxyData[galaxyId];
164
165 if (!galaxy) {
166 return (
167 <div className="galaxy-not-found">
168 <h3>Galaxy Not Found</h3>
169 <p>No galaxy found with identifier: {galaxyId}</p>
170 <button onClick={() => navigate('/galaxies')}>
171 Return to galaxy list
172 </button>
173 </div>
174 );
175 }
176
177 return (
178 <div className="galaxy-details">
179 <h3>Galaxy: {galaxy.name}</h3>
180
181 <nav className="galaxy-nav">
182 <NavLink to="" end>Overview</NavLink>
183 <NavLink to="systems">Solar Systems</NavLink>
184 <NavLink to="map">Galaxy Map</NavLink>
185 </nav>
186
187 <div className="galaxy-content">
188 <Outlet context={{ galaxy }} />
189 </div>
190
191 <div className="back-navigation">
192 <Link to="/galaxies">← Return to galaxy list</Link>
193 </div>
194 </div>
195 );
196}
197
198// Galaxy overview (default view)
199function GalaxyOverview() {
200 const { galaxy } = useOutletContext();
201
202 return (
203 <div className="galaxy-overview">
204 <h4>Overview: {galaxy.name}</h4>
205 <table className="galaxy-stats">
206 <tbody>
207 <tr>
208 <th>Type:</th>
209 <td>{galaxy.type}</td>
210 </tr>
211 <tr>
212 <th>Diameter:</th>
213 <td>{galaxy.diameter}</td>
214 </tr>
215 <tr>
216 <th>Number of stars:</th>
217 <td>{galaxy.stars}</td>
218 </tr>
219 <tr>
220 <th>Discovered systems:</th>
221 <td>{galaxy.systems.length}</td>
222 </tr>
223 </tbody>
224 </table>
225
226 <h5>Fun Facts</h5>
227 <p>Unique fun facts about galaxy {galaxy.name} will appear here...</p>
228 </div>
229 );
230}
231
232// Solar systems list in galaxy
233function GalaxySystems() {
234 const { galaxy } = useOutletContext();
235 const { galaxyId } = useParams();
236
237 return (
238 <div className="galaxy-systems">
239 <h4>Solar Systems in {galaxy.name}</h4>
240
241 {galaxy.systems.length > 0 ? (
242 <ul className="systems-list">
243 {galaxy.systems.map(system => (
244 <li key={system.id} className="system-item">
245 <div className="system-info">
246 <h5>{system.name}</h5>
247 </div>
248 <Link to={`/galaxies/${galaxyId}/systems/${system.id}`}>
249 Explore system
250 </Link>
251 </li>
252 ))}
253 </ul>
254 ) : (
255 <p>No systems have been discovered in this galaxy yet.</p>
256 )}
257 </div>
258 );
259}
260
261// Galaxy map
262function GalaxyMap() {
263 const { galaxy } = useOutletContext();
264
265 return (
266 <div className="galaxy-map">
267 <h4>Galaxy Map: {galaxy.name}</h4>
268 <div className="map-placeholder">
269 <p>An interactive map of galaxy {galaxy.name} will appear here.</p>
270 <p>Currently under development...</p>
271 </div>
272 </div>
273 );
274}
275
276// ===== SOLAR SYSTEMS SECTION =====
277
278// Solar system details
279function SolarSystemDetails() {
280 const { galaxyId, systemId } = useParams();
281 const navigate = useNavigate();
282
283 // In a real application, data would be fetched based on galaxyId and systemId
284 const systemData = {
285 'solar': {
286 name: 'Solar System',
287 star: 'Sun',
288 planets: 8,
289 age: '4.6 billion years'
290 },
291 'alpha-centauri': {
292 name: 'Alpha Centauri',
293 star: 'Alpha Centauri A and B, Proxima Centauri',
294 planets: 3,
295 age: '4.85 billion years'
296 },
297 'sirius': {
298 name: 'Sirius',
299 star: 'Sirius A and B',
300 planets: 0,
301 age: '230 million years'
302 },
303 'system-1': {
304 name: 'System M31-A',
305 star: 'Star M31-A',
306 planets: 5,
307 age: '2.3 billion years'
308 },
309 'system-2': {
310 name: 'System M31-B',
311 star: 'Star M31-B',
312 planets: 7,
313 age: '3.1 billion years'
314 }
315 };
316
317 const system = systemData[systemId];
318
319 if (!system) {
320 return (
321 <div className="system-not-found">
322 <h4>System Not Found</h4>
323 <p>No solar system found with identifier: {systemId}</p>
324 <button onClick={() => navigate(`/galaxies/${galaxyId}/systems`)}>
325 Return to systems list
326 </button>
327 </div>
328 );
329 }
330
331 return (
332 <div className="system-details">
333 <h4>Solar System: {system.name}</h4>
334
335 <table className="system-stats">
336 <tbody>
337 <tr>
338 <th>Star(s):</th>
339 <td>{system.star}</td>
340 </tr>
341 <tr>
342 <th>Number of planets:</th>
343 <td>{system.planets}</td>
344 </tr>
345 <tr>
346 <th>Age:</th>
347 <td>{system.age}</td>
348 </tr>
349 </tbody>
350 </table>
351
352 <div className="back-navigation">
353 <Link to={`/galaxies/${galaxyId}/systems`}>
354 ← Return to solar systems
355 </Link>
356 </div>
357 </div>
358 );
359}
360
361// ===== MISSIONS SECTION =====
362
363// Missions layout
364function MissionsLayout() {
365 return (
366 <div className="missions-section">
367 <h2>Space Missions</h2>
368 <Outlet />
369 </div>
370 );
371}
372
373// Missions list
374function MissionsList() {
375 const missions = [
376 { id: 'apollo', name: 'Apollo', status: 'Completed' },
377 { id: 'artemis', name: 'Artemis', status: 'In Progress' },
378 { id: 'mars-2030', name: 'Mars 2030', status: 'Planned' }
379 ];
380
381 return (
382 <div className="missions-list">
383 <h3>Space Missions</h3>
384 <ul>
385 {missions.map(mission => (
386 <li key={mission.id}>
387 <Link to={mission.id}>{mission.name}</Link> - {mission.status}
388 </li>
389 ))}
390 </ul>
391 </div>
392 );
393}
394
395// Mission details
396function MissionDetails() {
397 const { missionId } = useParams();
398
399 return (
400 <div className="mission-details">
401 <h3>Mission details: {missionId}</h3>
402 <p>Details for mission {missionId} will appear here...</p>
403 <Link to="/missions">← Return to missions list</Link>
404 </div>
405 );
406}
407
408// ===== CREW SECTION =====
409
410// Crew layout
411function CrewLayout() {
412 return (
413 <div className="crew-section">
414 <h2>Crew Management</h2>
415 <Outlet />
416 </div>
417 );
418}
419
420// Crew members list
421function CrewList() {
422 const crewMembers = [
423 { id: 'crew1', name: 'John Smith', role: 'Captain' },
424 { id: 'crew2', name: 'Anna Johnson', role: 'Navigator' },
425 { id: 'crew3', name: 'Peter Williams', role: 'Engineer' }
426 ];
427
428 return (
429 <div className="crew-list">
430 <h3>Crew Members</h3>
431 <ul>
432 {crewMembers.map(member => (
433 <li key={member.id}>
434 <Link to={member.id}>{member.name}</Link> - {member.role}
435 </li>
436 ))}
437 </ul>
438 </div>
439 );
440}
441
442// Crew member details
443function CrewMemberDetails() {
444 const { crewId } = useParams();
445
446 return (
447 <div className="crew-member-details">
448 <h3>Crew member profile: {crewId}</h3>
449 <p>Profile for crew member {crewId} will appear here...</p>
450 <Link to="/crew">← Return to crew list</Link>
451 </div>
452 );
453}
454
455// ===== MAIN APPLICATION COMPONENT =====
456
457function App() {
458 return (
459 <BrowserRouter>
460 <Routes>
461 <Route path="/" element={<AppLayout />}>
462 {/* Home page */}
463 <Route index element={<Dashboard />} />
464
465 {/* Galaxies section */}
466 <Route path="galaxies" element={<GalaxiesLayout />}>
467 <Route index element={<GalaxiesList />} />
468 <Route path=":galaxyId" element={<GalaxyLayout />}>
469 <Route index element={<GalaxyOverview />} />
470 <Route path="systems" element={<GalaxySystems />} />
471 <Route path="map" element={<GalaxyMap />} />
472 </Route>
473 <Route path=":galaxyId/systems/:systemId" element={<SolarSystemDetails />} />
474 </Route>
475
476 {/* Missions section */}
477 <Route path="missions" element={<MissionsLayout />}>
478 <Route index element={<MissionsList />} />
479 <Route path=":missionId" element={<MissionDetails />} />
480 </Route>
481
482 {/* Crew section */}
483 <Route path="crew" element={<CrewLayout />}>
484 <Route index element={<CrewList />} />
485 <Route path=":crewId" element={<CrewMemberDetails />} />
486 </Route>
487
488 {/* 404 handling */}
489 <Route path="*" element={<NotFound />} />
490 </Route>
491 </Routes>
492 </BrowserRouter>
493 );
494}
495
496// Component for non-existent paths
497function NotFound() {
498 return (
499 <div className="not-found">
500 <h2>404 - Lost in Space</h2>
501 <p>This path does not lead to any known destination in space.</p>
502 <Link to="/">Return to Command Center</Link>
503 </div>
504 );
505}
506
507export default App;Nested routes in React Router are a powerful technique for organizing navigation in React applications, allowing for the creation of intuitive, hierarchical user interface structures. Key benefits:
Just as in real space exploration, where we travel from a galaxy, through solar systems, to planets and moons - nested routes allow creating intuitive navigation systems that guide the user through different levels of our space application.