Without notes, state yesterday’s main idea and one unresolved question.
Routing and page structure
React Frontend Fundamentals
Objective
Map URLs to pages and preserve understandable navigation.
A React component is a reusable functional block with inputs (props), internal state, and rendered output.
- client-side routing
- route parameters
- not-found states
Why this matters
Your app is one page that changes. That is fine until someone wants to bookmark an equipment record, send a colleague a link to it, or press the back button. Right now none of those work, because every screen has the same URL. Today you give each screen an address.
By the end of the hour, typing /equipment/PUMP-3 into the address bar opens that record directly,
back and forward behave, and a made-up URL shows a page that helps rather than a blank screen.
Client-side routing
On Day 9 you linked pages together with <a href="about.html">. Clicking one makes the browser
throw away the current document, request a new one, and rebuild everything. In a React app that
also destroys all your state and re-runs every effect.
Client-side routing keeps a single HTML document and swaps components instead. A router does three jobs:
- it reads the current URL path,
- it renders the component you mapped to that path,
- it intercepts internal link clicks and pushes the new URL into browser history without a page load.
Point three is what keeps the URL honest. The address bar, the back button, and bookmarks all keep working — the browser's history is updated, just not by requesting a new document.
Install the standard library for this:
npm install react-router-dom
An address decoder
A memory bus puts an address on the lines and a decoder enables exactly one device to respond.
Routing is the same decode step: the URL path is the address, the route table is the decoder, and
one component gets enabled to drive the output. /equipment/PUMP-3 selects one device and hands
it the low-order bits — the id — as its input.
Setting up the route table
The router needs to wrap your whole app so every component inside can see the current URL. Do that
once, in src/main.tsx:
import { BrowserRouter } from "react-router-dom";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);
Then declare the mapping in App.tsx. <Routes> picks the single best match among its <Route>
children and renders that route's element:
import { Routes, Route, NavLink, Outlet } from "react-router-dom";
function Layout() {
return (
<>
<header>
<h1>Equipment Maintenance</h1>
<nav aria-label="Main">
<NavLink to="/">Dashboard</NavLink>
<NavLink to="/equipment">Equipment</NavLink>
<NavLink to="/login">Log in</NavLink>
</nav>
</header>
<main>
<Outlet />
</main>
</>
);
}
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<DashboardPage />} />
<Route path="equipment" element={<EquipmentListPage />} />
<Route path="equipment/:id" element={<EquipmentDetailPage />} />
<Route path="login" element={<LoginPage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}
The outer <Route> has no path, only an element. It is a layout route: it always renders,
and <Outlet /> marks the hole where the matched child page goes. That is how the header and nav
stay on screen while the page below them changes — written once, not copied into four files.
index means "the parent's path with nothing after it", so that route handles /.
Link and NavLink instead of <a>. Both render a real <a> element in the DOM — so
right-click, open-in-new-tab, and screen readers all behave normally — but they intercept the plain
click and route without reloading. NavLink adds one thing: when its to matches the current URL
it sets aria-current="page" on the link, which announces "current page" to assistive technology
and gives you a hook for styling.
A plain `<a href="/equipment">` still reloads the page
It works, in that the right screen appears — but the whole app restarts, state is lost, and every
effect runs again. It usually feels like a slow flicker. Use <a> only for links leaving your
site.
Route parameters
Most URLs identify a resource: not "the equipment page" but this specific machine. A segment starting with a colon is a route parameter — a wildcard that matches any value and hands it to you by name.
path="equipment/:id" matches /equipment/PUMP-3 id = "PUMP-3"
matches /equipment/MTR-1 id = "MTR-1"
Read it inside the component with useParams:
import { useParams, Link } from "react-router-dom";
export function EquipmentDetailPage() {
const { id } = useParams();
const item = equipment.find((entry) => entry.id === id);
// ...
}
useParams returns an object whose keys are your parameter names. The values are typed
string | undefined, because TypeScript cannot know which route rendered this component — so you
have to handle the missing case, which is exactly the case you should be handling anyway.
This mirrors the API you built on Day 41: GET /api/equipment/:id. Same shape, same reasoning.
Keep URLs resource-oriented and predictable — plural collection, then id — and a colleague can
guess them.
A room number, not a floor plan
"Building 2, room 314" identifies one room; you can write it on a note and hand it to anyone. A URL without a parameter is like saying "the room I was looking at" — meaningless to the next person. Route parameters are what make a screen shareable.
Not-found states
There are two different ways a URL can fail, and they need two different answers.
The path matches nothing. /equpiment is a typo, and no route covers it. That is what
path="*" catches — a catch-all that must be listed so it only wins when nothing else does. Give
it a page with a heading, a short explanation, and a link back to somewhere real. Never a blank
screen.
The path matches but the resource does not exist. /equipment/NOPE-9 matches
equipment/:id perfectly; there is just no such machine. The route renders, find returns
undefined, and the component must say so:
if (!item) {
return (
<section>
<h2>Equipment not found</h2>
<p>No equipment matches the id "{id}".</p>
<Link to="/equipment">Back to all equipment</Link>
</section>
);
}
That if (!item) is not defensive padding. Once the data comes from a server in Week 10, deleted
records will produce exactly this, from links that were valid yesterday.
Direct URLs break on a real server unless you configure it
Vite's dev server sends index.html for any unknown path, so /equipment/PUMP-3 works while you
develop. A plain static host does not: it looks for a file at that path and returns 404. Every
host has a setting for this, usually called a history or SPA fallback, and it is the number one
"works locally, 404s in production" bug for single-page apps. Week 11 configures it properly.
Walkthrough
Create src/pages/ with one file per page, then wire the table above.
mkdir -p src/pages
Each page component is ordinary React — no new concepts, just a screen:
export function EquipmentListPage() {
return (
<section>
<h2>All equipment</h2>
<ul>
{equipment.map((item) => (
<li key={item.id}>
<Link to={`/equipment/${item.id}`}>{item.name}</Link>
</li>
))}
</ul>
</section>
);
}
Start the dev server and click a name. The URL changes to /equipment/PUMP-3, the header stays put,
and only the region inside <Outlet /> changes. Press back: you return to the list. Now copy the
detail URL, open a new tab, and paste it — the record loads directly, because the route table reads
the address rather than remembering a click.
Checkpoint
With the detail page open, look at the DevTools Network tab and press back. No document request appears. Say why: the router changed history and re-rendered, it did not ask the server for a new page.
Pair mode — before you write the route table
Today's mode is pair: the AI proposes, you verify. Before accepting any generated change, inspect the diff, run your checks, and be able to explain the behaviour of every route you keep.
"Propose a route tree from these user journeys. Keep URLs resource-oriented and predictable."
List your journeys first — "see today's status", "look up one machine", "log in". Then judge the proposal against them. If a route does not serve a journey, cut it.
Your turn
Deliverable: direct URLs work and unknown routes show a useful page.
- Install
react-router-domand wrap<App />in<BrowserRouter>inmain.tsx. - Create four pages in
src/pages/:DashboardPage,EquipmentListPage,EquipmentDetailPage,LoginPage. Each returns a<section>with its own<h2>. - Build the route table with the pathless layout route,
index,equipment,equipment/:id,login, and*. - Put the header and a
<nav aria-label="Main">ofNavLinks in the layout, with<Outlet />below in a<main>element. - In
EquipmentListPage, link each item to/equipment/${item.id}withLink. - In
EquipmentDetailPage, readidwithuseParams, look the item up, and render the not-found block when it is missing. - Create
NotFoundPagewith a heading and a link home, and confirm/nopeshows it. - Test four URLs by typing them in the address bar:
/,/equipment,/equipment/PUMP-3, and/equipment/NOPE-9. The last two must give different pages. - Keyboard pass: Tab to a nav link and press Enter. Confirm the active link
carries
aria-current="page"in the DevTools element inspector.
You are done when
Four URLs work when pasted fresh into the address bar, back and forward move between them, and both kinds of not-found produce a page with a way out.
Common pitfalls
- Forgetting
<BrowserRouter>. Any router hook then throws, with a message about needing to be used within a<Router>. Wrap the app once, at the root. - Putting
path="*"first.<Routes>picks the best match rather than the first, so this is less fatal than it looks — but keep the catch-all last so the table reads in the order a person would. - Using
<a href>for internal links. The screen is right and the app restarted. Watch for the flash and the lost state. - Assuming
useParamsgives you a valid id. It gives you whatever was in the URL. Always handle "no such resource".
Verify it yourself
Open today's reference, React's Learn section, and find where it discusses routing and what React itself does not provide.
- React points to routers rather than shipping one. Find that statement and note which libraries it names. Why does routing sit outside React's own scope?
- In the React Router documentation, find
useNavigate. Write one sentence on when you would navigate from code instead of using aLink— you will need it on Day 66 after a login submit.
Record both in notes/day-62.md. Knowing which problems React deliberately leaves to other
libraries is part of knowing what React is.
The hour
- 0–5 min Recall
- 5–20 min Learn
Read only the listed concept notes and official reference sections needed today.
- 20–48 min Build
Create dashboard, equipment list, equipment detail, and login routes.
- 48–55 min Explain and verify
Run the result, inspect evidence, and explain the data/control flow in your own words.
- 55–60 min Quiz and commit
Complete the quiz, record one lesson, and commit the verified change when applicable.
What to hand in
Direct URLs work and unknown routes show a useful page.
Working with AI today
Define one small task, review the plan, inspect the diff, run checks, and explain every changed section.
Propose a route tree from these user journeys. Keep URLs resource-oriented and predictable.
References
End-of-day quiz
Explain-back gate
Pass the quiz above to unlock completion.
Quiz + explain-back checks required.