← Front-End Developer — all chapters  ·  Kelick Tech Hub

Chapter 32 — Routing and Single-Page Apps

Section 1 — What a single-page app is

A single-page application is a web app that loads one HTML page and then updates the screen in place, instead of asking the server for a whole new page on every click. That definition is worth unpacking slowly, because a single-page app shows up in almost every React screen you will build. When you can say in plain words what a single-page app does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It feels fast and app-like, because only the changed part of the screen updates, while navigation, headers, and state can stay in place.

Working with a single-page app usually follows a small, repeatable pattern. First, load the app shell once. Next, change the displayed view when the route changes. Finally, update the address bar to match the view. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function App() {
  return <main><Home /></main>;
}

Read the example from the top down. App shows Home inside the same page shell, and routing will later swap Home for another view without reloading the page. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with a single-page app is reloading the whole page for every view change, which throws away app state and feels slow. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: In plain words, what is a single-page app?

Section 2 — Single-page versus multi-page apps

A multi-page app asks the server for a new document on every navigation, while a single-page app swaps views inside the loaded page and updates the address. That definition is worth unpacking slowly, because single-page versus multi-page shows up in almost every React screen you will build. When you can say in plain words what single-page versus multi-page does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The trade-off is real: single-page apps feel responsive and keep state, while multi-page apps are simpler and often easier for search engines to read.

Working with single-page versus multi-page usually follows a small, repeatable pattern. First, ask whether state should survive navigation. Next, ask whether each view needs its own document. Finally, choose the model that fits the product, not the fashion. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function App() {
  return <div><Header /><Home /></div>;
}

Read the example from the top down. Header stays mounted while Home swaps for another view, which is the single-page benefit in miniature. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with single-page versus multi-page is choosing a single-page app for a simple document site that would be simpler as separate pages. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What is the main difference between a single-page and a multi-page app?

Section 3 — How routing works

Routing is mapping an address to a view: when the address changes, the router finds the matching route and renders its component. That definition is worth unpacking slowly, because routing shows up in almost every React screen you will build. When you can say in plain words what routing does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It turns the address bar into a control for the app, so a link, a bookmark, or a shared address can open the right view directly.

Working with routing usually follows a small, repeatable pattern. First, define a table of paths and components. Next, watch the current address. Finally, render the component whose path matches. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const routes = [{ path: "/", view: Home }, { path: "/about", view: About }];

Read the example from the top down. This tiny table is the whole idea: a path points at a view, and the router looks the path up when it changes. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with routing is rendering views from clicks alone without updating the address, so bookmarks and sharing break. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What does a router map an address to?

Section 4 — The History API

The History API is the browser feature that lets a single-page app change the address bar and manage back and forward navigation without reloading the page. That definition is worth unpacking slowly, because the History API shows up in almost every React screen you will build. When you can say in plain words what the History API does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It is what makes single-page navigation feel like real navigation, with working back buttons and shareable addresses.

Working with the History API usually follows a small, repeatable pattern. First, push a new entry when the user navigates. Next, listen for back and forward movements. Finally, render the view that matches the new address. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

history.pushState(null, "", "/about");

Read the example from the top down. PushState changes the address to about without reloading, and the router can then render the About view. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with the History API is changing the address manually without telling the router, so the screen and the address disagree. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What does the History API let a single-page app do?

Section 5 — The URL as interface state

Treating the URL as interface state means the address itself records which view, item, filter, or page the user is looking at. That definition is worth unpacking slowly, because the URL as state shows up in almost every React screen you will build. When you can say in plain words what the URL as state does, you can decide where it belongs, what data it needs, and what should happen when that data changes. When the address records the state, sharing, bookmarking, and refreshing all work naturally, because the address can rebuild the view.

Working with the URL as state usually follows a small, repeatable pattern. First, put view and selection in the path or query. Next, read that state from the address on load. Finally, update the address when the selection changes. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const path = "/products/42"; // item 42

Read the example from the top down. Product 42 is recorded in the path itself, so opening that address later shows the same product. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with the URL as state is keeping the current item only in memory, so refreshing the page loses the selection. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why put interface state in the URL?

Section 6 — Routes and a route table

A route table is a list of routes, each pairing a path pattern with the component that should render for that path. That definition is worth unpacking slowly, because a route table shows up in almost every React screen you will build. When you can say in plain words what a route table does, you can decide where it belongs, what data it needs, and what should happen when that data changes. A central table makes navigation easy to audit: you can see every view, its path, and its place in the app at a glance.

Working with a route table usually follows a small, repeatable pattern. First, list every view the app needs. Next, give each view a clear path. Finally, keep the table in one place where it can be reviewed. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const routes = [{ path: "/", element: Home }, { path: "/settings", element: Settings }];

Read the example from the top down. Home and Settings each have a path, and the router chooses between them by matching the current address. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with a route table is scattering route definitions across many files with no central list to review. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What is a route table?

Section 7 — Nested routes

Nested routes are routes defined inside a parent route, so a child view renders inside the parent layout for a longer, combined path. That definition is worth unpacking slowly, because nested routes shows up in almost every React screen you will build. When you can say in plain words what nested routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Nesting matches how interfaces are built: a dashboard layout stays visible while its inner views change.

Working with nested routes usually follows a small, repeatable pattern. First, define a parent route with a layout. Next, define child routes under it. Finally, render child content through an outlet in the parent. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Dashboard() {
  return <div><Nav /><Outlet /></div>;
}

Read the example from the top down. Dashboard keeps its navigation visible, while Outlet is the placeholder where the child view appears. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with nested routes is duplicating the parent layout in every child view instead of nesting under one layout route. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What stays visible when a nested child route changes?

Section 8 — Layout routes

A layout route is a route that renders shared structure, such as a header, sidebar, or footer, around its child routes. That definition is worth unpacking slowly, because layout routes shows up in almost every React screen you will build. When you can say in plain words what layout routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Layouts prevent duplication and keep shared parts mounted, so their state survives when a child view changes.

Working with layout routes usually follows a small, repeatable pattern. First, identify structure shared by several views. Next, make a layout component for that structure. Finally, render child routes inside it through an outlet. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Layout() {
  return <div><Header /><Outlet /><Footer /></div>;
}

Read the example from the top down. Header and Footer stay in place, while Outlet swaps the main content, which is exactly what a layout should do. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with layout routes is copying the same header and footer markup into every view component. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What does a layout route render around its children?

Section 9 — Index routes

An index route is the default child view rendered when a parent path is visited without a further child segment. That definition is worth unpacking slowly, because index routes shows up in almost every React screen you will build. When you can say in plain words what index routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It gives a parent a sensible default, such as a dashboard overview, instead of a blank outlet when no child is chosen.

Working with index routes usually follows a small, repeatable pattern. First, define the parent route. Next, add an index child for the default view. Finally, let the router render it when the parent path matches exactly. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const dashboard = { path: "/dashboard", children: [{ index: true, element: Overview }] };

Read the example from the top down. Visiting dashboard shows Overview by default, while deeper dashboard paths show their own child views. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with index routes is leaving a parent outlet blank, so visiting the parent path shows an empty main area. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What does an index route render?

Section 10 — Dynamic segments and parameters

A dynamic segment is a part of a path that captures a variable value, such as an item id, instead of matching one fixed word. That definition is worth unpacking slowly, because dynamic segments shows up in almost every React screen you will build. When you can say in plain words what dynamic segments does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Dynamic segments let one route serve many items: a single product route can show any product, depending on the captured id.

Working with dynamic segments usually follows a small, repeatable pattern. First, write the segment with a colon and a name, such as product id. Next, read that parameter in the component. Finally, use it to load or find the matching item. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const route = "/products/:id";

Read the example from the top down. This pattern matches products slash any id, and the component can read that id to decide which product to show. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with dynamic segments is creating a separate route for every item, instead of one dynamic route for all of them. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What does a dynamic segment capture?

Section 11 — Reading route parameters

Reading route parameters means asking the router for the values captured from the current path, such as the id in a product address. That definition is worth unpacking slowly, because reading route parameters shows up in almost every React screen you will build. When you can say in plain words what reading route parameters does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Parameters connect the address to data: the component uses the captured value to find and display the right item.

Working with reading route parameters usually follows a small, repeatable pattern. First, define the dynamic segment in the route. Next, read the parameter in the component. Finally, handle a missing or unknown value gracefully. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Product() {
  const { id } = useParams();
  return <p>Product {id}</p>;
}

Read the example from the top down. Product reads id from the address and displays it, so the same component serves every product address. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with reading route parameters is assuming a parameter is always present, and crashing when a malformed address omits it. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: How does a component read a route parameter?

Section 12 — Link versus anchor

A router Link changes the view inside the single-page app without reloading, while a plain anchor asks the browser for a new document. That definition is worth unpacking slowly, because Link versus anchor shows up in almost every React screen you will build. When you can say in plain words what Link versus anchor does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Using Link for internal navigation keeps app state alive and feels fast, while anchors remain correct for external sites.

Working with Link versus anchor usually follows a small, repeatable pattern. First, use Link for routes inside your app. Next, use a plain anchor for external addresses. Finally, check that internal clicks do not reload the page. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Nav() {
  return <Link to="/about">About</Link>;
}

Read the example from the top down. Clicking this Link changes the view to About inside the app, without a full page reload. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with Link versus anchor is using plain anchors for internal navigation, which reloads the app and loses its state. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why use a router Link instead of a plain anchor inside an app?

Section 13 — NavLink and active styling

A NavLink is a link that knows whether its route is currently active, so it can apply a different style to the current section. That definition is worth unpacking slowly, because NavLink active styling shows up in almost every React screen you will build. When you can say in plain words what NavLink active styling does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Active styling tells users where they are, which is essential navigation feedback in any app with several sections.

Working with NavLink active styling usually follows a small, repeatable pattern. First, use NavLink for main navigation items. Next, style the active state differently, such as bold or underlined. Finally, check that only the intended link is active. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Nav() {
  return <NavLink to="/home">Home</NavLink>;
}

Read the example from the top down. When the home route is active, this link can be styled as current, while other links stay in their normal style. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with NavLink active styling is styling every navigation link the same, so users cannot tell which section they are in. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What extra information does a NavLink provide?

Section 14 — Programmatic navigation

Programmatic navigation means changing the route from code, such as after a form is saved, rather than from a user clicking a link. That definition is worth unpacking slowly, because programmatic navigation shows up in almost every React screen you will build. When you can say in plain words what programmatic navigation does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It is for flows where navigation is a consequence of an action: save, then go to the list; log in, then go home.

Working with programmatic navigation usually follows a small, repeatable pattern. First, perform the action, such as saving. Next, on success, navigate to the next route. Finally, on failure, stay and show the error. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Save() {
  const navigate = useNavigate();
  return <button onClick={() => { save(); navigate("/list"); }}>Save</button>;
}

Read the example from the top down. After saving, the code navigates to the list view, completing the flow without requiring another click. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with programmatic navigation is navigating before an action succeeds, so the user lands on a list that does not yet contain the new item. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: When should you navigate from code rather than from a link?

Section 15 — Redirects

A redirect sends the user from one route to another automatically, for example from an old path to its replacement or from a guarded page to login. That definition is worth unpacking slowly, because redirects shows up in almost every React screen you will build. When you can say in plain words what redirects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Redirects keep old addresses working and enforce access rules, without requiring the user to guess the correct address.

Working with redirects usually follows a small, repeatable pattern. First, decide which path should redirect and where it should go. Next, render a redirect component for that path. Finally, preserve useful information, such as the intended destination, when redirecting. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Old() {
  return <Navigate to="/new" replace />;
}

Read the example from the top down. Visiting the old path automatically lands on the new path, and replace avoids adding the old path to the back stack. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with redirects is redirecting in a loop, where two routes redirect to each other forever. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What is a redirect for?

Section 16 — Not found and catch-all routes

A catch-all route matches any address that no other route matches, and usually renders a helpful not-found page. That definition is worth unpacking slowly, because catch-all routes shows up in almost every React screen you will build. When you can say in plain words what catch-all routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It turns a dead end into guidance: the user learns the address was wrong and gets links to real views.

Working with catch-all routes usually follows a small, repeatable pattern. First, place the catch-all last in the route table. Next, render a clear not-found message. Finally, offer links back to the home or main views. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const notFound = { path: "*", element: NotFound };

Read the example from the top down. The star pattern catches every unmatched address, so no address renders a blank page. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with catch-all routes is leaving unmatched addresses blank, so users cannot tell whether the app broke or the address was wrong. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What should a catch-all route render?

Section 17 — Query strings and search parameters

A query string is the part of an address after a question mark, carrying key and value pairs such as search terms, filters, or page numbers. That definition is worth unpacking slowly, because query strings shows up in almost every React screen you will build. When you can say in plain words what query strings does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Query strings suit optional, shareable state: a filtered list address can be copied and will show the same filtered list.

Working with query strings usually follows a small, repeatable pattern. First, read search parameters from the address. Next, update them when a filter changes. Finally, keep them readable and optional. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const params = new URLSearchParams("?q=shoes");
params.get("q"); // shoes

Read the example from the top down. The q parameter holds the search term, and the component can read it to filter its list. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with query strings is hiding filter state only in memory, so a shared filtered link shows the unfiltered list. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What kind of state belongs in a query string?

Section 18 — Protected routes

A protected route is a route that only renders when a condition holds, such as the user being signed in, and otherwise redirects. That definition is worth unpacking slowly, because protected routes shows up in almost every React screen you will build. When you can say in plain words what protected routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Protection enforces access rules at the route level, so a private view cannot be reached simply by typing its address.

Working with protected routes usually follows a small, repeatable pattern. First, check the access condition in a wrapper route. Next, render the child view when it passes. Finally, redirect to login when it fails. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Protected({ children }) {
  return signedIn ? children : <Navigate to="/login" />;
}

Read the example from the top down. Protected renders its children only when signed in, and otherwise sends the user to login. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with protected routes is hiding a link to a private view but leaving the route itself unprotected. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: How does a protected route enforce access?

Section 19 — Route guards and login redirects

A route guard checks an access condition before rendering, and a login redirect sends a failed check to the sign-in view. That definition is worth unpacking slowly, because route guards shows up in almost every React screen you will build. When you can say in plain words what route guards does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Guards centralise access logic, so every protected view follows the same rule instead of each view inventing its own.

Working with route guards usually follows a small, repeatable pattern. First, write one guard component for the condition. Next, wrap protected routes in it. Finally, redirect failures to login with the intended destination recorded. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Guard({ children }) {
  return allowed ? children : <Navigate to="/login" />;
}

Read the example from the top down. Guard is reusable: any route wrapped in it gets the same access check and the same login redirect. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with route guards is duplicating access checks in every view, so a rule change requires editing many files. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why centralise access checks in a guard?

Section 20 — Lazy loading routes

Lazy loading a route means loading its code only when the user first visits it, instead of loading every view up front. That definition is worth unpacking slowly, because lazy loading routes shows up in almost every React screen you will build. When you can say in plain words what lazy loading routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It keeps the first load fast, because the browser downloads only the code needed for the first view and fetches the rest on demand.

Working with lazy loading routes usually follows a small, repeatable pattern. First, split each route view into its own code chunk. Next, load it when its route is first visited. Finally, show a loading fallback while it loads. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const About = lazy(() => import("./About"));

Read the example from the top down. About code is fetched only when the About route is visited, not when the app first loads. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with lazy loading routes is lazy loading a tiny view, where the extra request costs more than the code it saves. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why lazy load route code?

Section 21 — Scroll restoration

Scroll restoration means returning the scroll position to a sensible place when the route changes, usually the top of the new view. That definition is worth unpacking slowly, because scroll restoration shows up in almost every React screen you will build. When you can say in plain words what scroll restoration does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Without it, a user can land halfway down a new view, because the page kept the scroll position from the previous view.

Working with scroll restoration usually follows a small, repeatable pattern. First, reset scroll to the top on route change. Next, preserve position for back and forward where that feels right. Finally, test with long pages in both directions. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

window.scrollTo(0, 0); // on route change

Read the example from the top down. This simple reset puts the new view at its top, which is what users expect from a fresh page. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with scroll restoration is leaving scroll position unchanged on every navigation, so new views open mid-page. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What should happen to scroll position on a new route?

Section 22 — Route transitions and loading states

A route loading state is what the user sees while a new view or its data is preparing, such as a spinner or a skeleton screen. That definition is worth unpacking slowly, because route loading states shows up in almost every React screen you will build. When you can say in plain words what route loading states does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Loading states prevent a blank or frozen feeling during navigation, and they tell the user the app is working.

Working with route loading states usually follows a small, repeatable pattern. First, show a fallback while the route code loads. Next, show a loading state while its data loads. Finally, replace the fallback with the real view when ready. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Loading() {
  return <p>Loading view</p>;
}

Read the example from the top down. A short loading message is honest and useful, while a blank screen leaves the user guessing. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with route loading states is showing nothing during a slow navigation, so users click again and trigger duplicate work. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What should a user see while a route is loading?

Section 23 — Data loading per route

Data loading per route means each route declares the data its view needs, so the data can be fetched as part of navigation. That definition is worth unpacking slowly, because data loading per route shows up in almost every React screen you will build. When you can say in plain words what data loading per route does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Route-level loading keeps data close to the view that needs it, and it lets the router show a loading state for the whole view.

Working with data loading per route usually follows a small, repeatable pattern. First, identify the data each view needs. Next, fetch it when the route is entered. Finally, render loading, error, or data states for that view. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function ProductPage() {
  const { id } = useParams();
  const product = useProduct(id);
  return <p>{product.name}</p>;
}

Read the example from the top down. ProductPage loads exactly the product its address names, and nothing else. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with data loading per route is fetching every view data in the top component, which couples unrelated views together. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why load data at the route level?

Section 24 — Error handling per route

Error handling per route means a failure in one view shows an error for that view, without breaking the whole app or other views. That definition is worth unpacking slowly, because route error handling shows up in almost every React screen you will build. When you can say in plain words what route error handling does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Isolating errors keeps navigation working: a failed product page should not destroy the header, sidebar, or the ability to go elsewhere.

Working with route error handling usually follows a small, repeatable pattern. First, catch errors at the route or view boundary. Next, render a clear error message for that view. Finally, offer a retry and a way back. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function ProductError() {
  return <p>Could not load this product. Try again.</p>;
}

Read the example from the top down. The error is contained to the product view, with a retry message, instead of a blank app. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with route error handling is letting a view error crash the entire app with no message and no way back. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why isolate errors per route?

Section 25 — Layouts with persistent header footer sidebar

A persistent layout keeps shared parts, such as a header or sidebar, mounted while child routes change underneath them. That definition is worth unpacking slowly, because persistent layouts shows up in almost every React screen you will build. When you can say in plain words what persistent layouts does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Persistence preserves layout state, such as an open sidebar section, and avoids re-rendering shared parts on every navigation.

Working with persistent layouts usually follows a small, repeatable pattern. First, build the layout once as a parent route. Next, render children through an outlet. Finally, keep layout state in the layout component. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function AppLayout() {
  return <div><Sidebar /><main><Outlet /></main></div>;
}

Read the example from the top down. Sidebar stays mounted while the main content swaps, so its state and position survive navigation. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with persistent layouts is rebuilding the layout inside every view, so shared state resets on every navigation. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What survives navigation in a persistent layout?

Section 26 — Breadcrumbs from routes

Breadcrumbs are a trail of links showing the path to the current view, often derived from the route segments. That definition is worth unpacking slowly, because breadcrumbs from routes shows up in almost every React screen you will build. When you can say in plain words what breadcrumbs from routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. They help users understand where they are in a hierarchy and jump back to a parent section in one click.

Working with breadcrumbs from routes usually follows a small, repeatable pattern. First, split the current path into segments. Next, turn each segment into a link to that level. Finally, show the current segment as plain text, not a link. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const crumbs = ["Home", "Products", "Shoes"];
return <nav>{crumbs.join(" > ")}</nav>;

Read the example from the top down. The trail reads as a hierarchy, and each earlier crumb is a way back to that level. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with breadcrumbs from routes is showing breadcrumbs that do not match the actual route hierarchy, which confuses more than it helps. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What do breadcrumbs show, and where do they come from?

Section 27 — Nested layouts and outlets

Nested layouts stack: an outer layout provides the app shell, and an inner layout provides a section shell, with outlets at each level. That definition is worth unpacking slowly, because nested layouts and outlets shows up in almost every React screen you will build. When you can say in plain words what nested layouts and outlets does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Stacking layouts lets different sections have different inner structure while sharing the outer app shell.

Working with nested layouts and outlets usually follows a small, repeatable pattern. First, define the outer layout with an outlet. Next, define inner layouts as child routes with their own outlets. Finally, place each view at the level whose layout it needs. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function SectionLayout() {
  return <div><SectionNav /><Outlet /></div>;
}

Read the example from the top down. SectionLayout adds section navigation around its children, inside the outer app layout. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with nested layouts and outlets is flattening all layouts into one giant layout, which forces every section to share the same inner structure. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: How do nested layouts and outlets work together?

Section 28 — Relative versus absolute links

An absolute link starts from the app root, while a relative link resolves from the current route, which matters inside nested routes. That definition is worth unpacking slowly, because relative versus absolute links shows up in almost every React screen you will build. When you can say in plain words what relative versus absolute links does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Relative links keep nested sections portable: moving a section under a new parent does not require rewriting every link inside it.

Working with relative versus absolute links usually follows a small, repeatable pattern. First, use absolute links for top-level navigation. Next, use relative links inside a nested section. Finally, check the resolved address when a link behaves unexpectedly. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Item() {
  return <Link to="details">Details</Link>;
}

Read the example from the top down. Inside a product route, details resolves relative to that product, which is usually what a nested link intends. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with relative versus absolute links is using absolute links everywhere inside nested routes, so moving a section breaks its internal links. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: When is a relative link better than an absolute one?

Section 29 — Route matching order and specificity

Route matching specificity means a more specific route wins over a general one, such as a product detail route winning over a product list pattern. That definition is worth unpacking slowly, because route matching specificity shows up in almost every React screen you will build. When you can say in plain words what route matching specificity does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Specificity prevents general patterns from swallowing specific addresses, which would show the wrong view for a detailed address.

Working with route matching specificity usually follows a small, repeatable pattern. First, list routes from most specific to most general where order matters. Next, check that dynamic segments do not swallow fixed paths. Finally, test fixed, dynamic, and catch-all addresses. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const routes = ["/products/new", "/products/:id", "*"];

Read the example from the top down. New is checked before the dynamic id pattern, so creating a product does not show a product with id new. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with route matching specificity is placing a catch-all or dynamic pattern before a fixed route it should not swallow. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why does route order and specificity matter?

Section 30 — Hash routing versus history routing

Hash routing stores the route after a hash in the address, while history routing uses the normal path, with the History API. That definition is worth unpacking slowly, because hash versus history routing shows up in almost every React screen you will build. When you can say in plain words what hash versus history routing does, you can decide where it belongs, what data it needs, and what should happen when that data changes. History routing gives clean, shareable addresses, while hash routing works without server support because the hash is never sent to the server.

Working with hash versus history routing usually follows a small, repeatable pattern. First, use history routing when the server can serve the app for any path. Next, use hash routing only when server fallback is unavailable. Finally, know which one your router is using before debugging addresses. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const hashRoute = "/#/about";
const historyRoute = "/about";

Read the example from the top down. Both point at About, but only the history form looks like a normal address and is sent to the server. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with hash versus history routing is choosing hash routing by default, leaving ugly addresses when history routing was available. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What is the difference between hash and history routing?

Section 31 — Server configuration for single-page apps

Server configuration for a single-page app means the server returns the app shell for any app path, so refreshing or deep-linking works. That definition is worth unpacking slowly, because SPA server configuration shows up in almost every React screen you will build. When you can say in plain words what SPA server configuration does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Without this fallback, a refresh on a deep address asks the server for a file that does not exist and returns a not-found error.

Working with SPA server configuration usually follows a small, repeatable pattern. First, configure the server to serve the app for unknown app paths. Next, serve real static files, such as images, directly. Finally, test refresh and direct entry on a deep route. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function serveApp(req) {
  return sendFile("index.html");
}

Read the example from the top down. This fallback returns the app shell, and the router then renders the view for the deep address. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with SPA server configuration is deploying a single-page app with no fallback, so only the home address works on refresh. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why does a single-page app need a server fallback?

Section 32 — Deep linking and sharing addresses

Deep linking means opening a specific view directly from its address, whether from a bookmark, a shared link, or a refresh. That definition is worth unpacking slowly, because deep linking shows up in almost every React screen you will build. When you can say in plain words what deep linking does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It works when the address fully describes the view, the server serves the app for that address, and the router renders the matching view.

Working with deep linking usually follows a small, repeatable pattern. First, make the address describe the view completely. Next, configure the server fallback. Finally, test the address in a fresh browser tab. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const link = "https://example.com/products/42";

Read the example from the top down. Anyone opening this link should land on product 42, not on the home page. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with deep linking is requiring several clicks to reach a view that its address should have described directly. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What must be true for deep linking to work?

Section 33 — Back and forward buttons

Back and forward buttons move through the history stack, and a single-page router must respond by rendering the view for each history entry. That definition is worth unpacking slowly, because back and forward shows up in almost every React screen you will build. When you can say in plain words what back and forward does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Respecting the history stack is essential: users expect back to return to the previous view, with its state as they left it where possible.

Working with back and forward usually follows a small, repeatable pattern. First, push a history entry for each real navigation. Next, replace, rather than push, for redirects and temporary steps. Finally, render the matching view on history movement. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

history.back(); // user presses back

Read the example from the top down. Going back pops the stack, and the router renders the previous view for the new address. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with back and forward is pushing a history entry for every trivial change, so back requires many clicks to escape. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What should happen when a user presses back in a single-page app?

Section 34 — Route state and location state

Location state is data passed along with a navigation, such as a message or the item just created, without putting it in the address. That definition is worth unpacking slowly, because location state shows up in almost every React screen you will build. When you can say in plain words what location state does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It suits temporary, non-shareable data: a success message after a save should not become part of a bookmarkable address.

Working with location state usually follows a small, repeatable pattern. First, pass state with the navigation call. Next, read it in the destination view. Finally, handle the case where it is absent, such as after a refresh. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

navigate("/list", { state: { message: "Saved" } });

Read the example from the top down. The list view can show Saved once, while the address itself stays clean and shareable. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with location state is putting a success message in the address, where it persists in bookmarks and shared links. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: When should you use location state instead of the address?

Section 35 — Search parameters for filters sorting pagination

Search parameters can hold filters, sort order, and page number, so a filtered or paged list has a shareable address. That definition is worth unpacking slowly, because search parameters for filters shows up in almost every React screen you will build. When you can say in plain words what search parameters for filters does, you can decide where it belongs, what data it needs, and what should happen when that data changes. This makes list state durable and shareable: a colleague opening the link sees the same filter, sort, and page.

Working with search parameters for filters usually follows a small, repeatable pattern. First, store filter, sort, and page in search parameters. Next, read them to build the list query. Finally, update them when the user changes a control. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const params = new URLSearchParams("?sort=price&page=2");

Read the example from the top down. Sort and page are recorded in the address, so the list view can be rebuilt exactly from it. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with search parameters for filters is keeping pagination only in memory, so sharing a page link shows page one. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What list state belongs in search parameters?

Section 36 — Modal routes

A modal route opens a detail view as an overlay while keeping the underlying list visible behind it, with its own address. That definition is worth unpacking slowly, because modal routes shows up in almost every React screen you will build. When you can say in plain words what modal routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It combines two benefits: the detail has a shareable address, and closing it returns the user to the list they were browsing.

Working with modal routes usually follows a small, repeatable pattern. First, define a route for the detail view. Next, render it as an overlay when navigated from the list. Finally, close it by navigating back to the list. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function PhotoModal() {
  return <div><p>Photo detail overlay</p></div>;
}

Read the example from the top down. The overlay shows the detail, while the list remains underneath, ready to return to on close. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with modal routes is opening a modal with no address, so sharing or refreshing loses the detail entirely. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What is a modal route?

Section 37 — Tabs via routes versus state

Tabs can be routes, when each tab deserves its own address, or state, when tabs are just a local view switch inside one screen. That definition is worth unpacking slowly, because tabs via routes versus state shows up in almost every React screen you will build. When you can say in plain words what tabs via routes versus state does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The choice follows shareability: if a tab should be linkable and bookmarkable, make it a route; otherwise, local state is simpler.

Working with tabs via routes versus state usually follows a small, repeatable pattern. First, ask whether a tab needs its own address. Next, if yes, use nested routes for the tabs. Finally, if no, use local state for the selected tab. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const [tab, setTab] = useState("details");

Read the example from the top down. Local tab state is enough when tabs are a small switch inside one view, with no need for separate addresses. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with tabs via routes versus state is making every tiny tab a route, which clutters the route table and history for no benefit. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: When should tabs be routes rather than state?

Section 38 — Search engine considerations for single-page apps

Search engine considerations for single-page apps centre on whether crawlers can see content that only appears after JavaScript runs. That definition is worth unpacking slowly, because SEO for single-page apps shows up in almost every React screen you will build. When you can say in plain words what SEO for single-page apps does, you can decide where it belongs, what data it needs, and what should happen when that data changes. If content is invisible to crawlers, it may not be indexed, so some apps pre-render or server-render important pages.

Working with SEO for single-page apps usually follows a small, repeatable pattern. First, identify which pages must be found by search. Next, ensure those pages render content without relying only on client JavaScript. Finally, test what a crawler or a no-JavaScript view actually sees. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Page() {
  return <article><h1>Guide title</h1><p>Guide text</p></article>;
}

Read the example from the top down. Content in the markup itself is visible, while content only fetched and inserted later may not be. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with SEO for single-page apps is assuming a search engine sees exactly what a logged-in user sees in the browser. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why can single-page apps be harder for search engines?

Section 39 — Accessibility in routing

Accessibility in routing means announcing route changes, moving focus sensibly, and keeping navigation usable with a keyboard and screen reader. That definition is worth unpacking slowly, because accessibility in routing shows up in almost every React screen you will build. When you can say in plain words what accessibility in routing does, you can decide where it belongs, what data it needs, and what should happen when that data changes. In a multi-page app the browser announces a new page on load, but in a single-page app that announcement must be provided deliberately.

Working with accessibility in routing usually follows a small, repeatable pattern. First, announce the new view title on route change. Next, move focus to the new main content or heading. Finally, keep links and buttons as real, focusable elements. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Main() {
  return <main><h1>New view title</h1></main>;
}

Read the example from the top down. A heading in the new main content gives focus and screen readers a clear landing point for the new view. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with accessibility in routing is changing views silently, so screen reader users do not know a new view has loaded. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What must routing provide that a page reload provides automatically?

Section 40 — Testing routes

Testing routes means checking that each address renders the expected view, that guards redirect correctly, and that links navigate as intended. That definition is worth unpacking slowly, because testing routes shows up in almost every React screen you will build. When you can say in plain words what testing routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Route tests catch the mistakes users feel most: a broken link, a guard that lets anyone through, or a catch-all that swallows a real route.

Working with testing routes usually follows a small, repeatable pattern. First, render the app at a test address. Next, assert the expected view appears. Finally, assert guards redirect and catch-alls show not-found. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function TestApp() {
  return <div>Test route view</div>;
}

Read the example from the top down. Even a tiny test app shows the pattern: start at an address, then check what the router rendered for it. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with testing routes is testing only the home route, while deeper, guarded, and not-found routes go untested. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What should route tests cover?

Section 41 — Organising route files

Organising route files means keeping the route table central and readable, with each view in its own file imported by the table. That definition is worth unpacking slowly, because organising route files shows up in almost every React screen you will build. When you can say in plain words what organising route files does, you can decide where it belongs, what data it needs, and what should happen when that data changes. A central table plus separate view files scales: adding a view is adding one file and one table entry.

Working with organising route files usually follows a small, repeatable pattern. First, keep the route table in one file. Next, give each view its own file. Finally, name routes and files after the view they serve. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

import Home from "./views/Home";
import About from "./views/About";

Read the example from the top down. Imports at the top of the route file make the table self-documenting: each path points at a named view file. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with organising route files is defining routes inline inside unrelated components, so no one can see the full navigation map. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: How should route files be organised?

Section 42 — Authorisation and roles per route

Roles per route means different routes require different roles, such as admin routes requiring an admin role, not merely being signed in. That definition is worth unpacking slowly, because roles per route shows up in almost every React screen you will build. When you can say in plain words what roles per route does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Role checks extend guards from who you are to what you may do, which is how admin and user areas stay properly separated.

Working with roles per route usually follows a small, repeatable pattern. First, define the required role for each protected route. Next, check role in the guard, not only sign-in. Finally, redirect or show a forbidden message when the role is missing. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function AdminGuard({ children }) {
  return role === "admin" ? children : <p>Not allowed</p>;
}

Read the example from the top down. AdminGuard renders its children only for admins, and otherwise shows a clear refusal instead of the admin view. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with roles per route is checking only sign-in for an admin route, so any signed-in user can reach it. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: How do role checks differ from sign-in checks?

Section 43 — Redirect after login

Redirect after login means sending the user back to the view they originally tried to reach, once sign-in succeeds. That definition is worth unpacking slowly, because redirect after login shows up in almost every React screen you will build. When you can say in plain words what redirect after login does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It respects the user intent: they did not ask for the home page, they asked for a specific view that required sign-in.

Working with redirect after login usually follows a small, repeatable pattern. First, record the intended destination when redirecting to login. Next, after successful sign-in, navigate to that destination. Finally, fall back to a sensible home view when no destination was recorded. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

navigate("/dashboard"); // after login, to intended view

Read the example from the top down. After login, the user lands on the dashboard they wanted, instead of being dumped on the home page. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with redirect after login is always sending users to the home page after login, regardless of what they were trying to reach. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Where should a user land after logging in?

Section 44 — Trailing slashes and case in paths

Trailing slashes and case in paths can make two addresses look different to a router even when a user means the same view. That definition is worth unpacking slowly, because trailing slashes and case shows up in almost every React screen you will build. When you can say in plain words what trailing slashes and case does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Consistent path rules prevent duplicate views and broken links: decide one form and stick to it across the app.

Working with trailing slashes and case usually follows a small, repeatable pattern. First, choose lowercase paths. Next, decide whether paths end with a slash. Finally, redirect the other form to the chosen form where needed. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const pathA = "/about";
const pathB = "/about/";

Read the example from the top down. These two look almost identical, but a strict router may treat them as different routes. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with trailing slashes and case is mixing case and trailing slashes freely, so the same view has several addresses. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why keep path case and trailing slashes consistent?

Section 45 — Prefetching route data

Prefetching route data means loading the next view data before the user navigates, such as when they hover over a link. That definition is worth unpacking slowly, because prefetching route data shows up in almost every React screen you will build. When you can say in plain words what prefetching route data does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It makes navigation feel instant, because the data is already ready when the view renders, instead of starting to load on arrival.

Working with prefetching route data usually follows a small, repeatable pattern. First, identify links users commonly follow next. Next, start fetching their data on hover or idle time. Finally, cache the result so navigation can use it immediately. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function PrefetchLink() {
  return <Link to="/details" onMouseEnter={prefetchDetails}>Details</Link>;
}

Read the example from the top down. Hovering starts the fetch, so clicking shortly after finds the data already available. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with prefetching route data is prefetching every possible route eagerly, which wastes bandwidth and slows the current view. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: How does prefetching make navigation feel faster?

Section 46 — Animating route changes

Animating route changes means adding a short transition, such as a fade, when one view replaces another. That definition is worth unpacking slowly, because animating route changes shows up in almost every React screen you will build. When you can say in plain words what animating route changes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. A gentle transition helps users understand that the view changed, rather than content simply jumping or flashing.

Working with animating route changes usually follows a small, repeatable pattern. First, keep the animation short, such as a quick fade. Next, animate the outgoing and incoming views consistently. Finally, respect users who prefer reduced motion. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Fade({ children }) {
  return <div>{children}</div>;
}

Read the example from the top down. Fade is a placeholder for a transition wrapper: the idea is a short, consistent change, not a long spectacle. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with animating route changes is adding long, distracting animations to every navigation, which slow users down. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What should a route animation achieve?

Section 47 — Multi-step forms via routes

Multi-step forms via routes give each step its own address, so the user can move forward, back, and refresh within a step. That definition is worth unpacking slowly, because multi-step forms via routes shows up in almost every React screen you will build. When you can say in plain words what multi-step forms via routes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Route steps make progress shareable and recoverable, and they let the browser back button move between steps naturally.

Working with multi-step forms via routes usually follows a small, repeatable pattern. First, define a route per step. Next, store the form data in a shared parent or state above the steps. Finally, validate each step before allowing the next. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const steps = ["/form/step-1", "/form/step-2", "/form/step-3"];
return <p>Step form</p>;

Read the example from the top down. Each step address is a checkpoint, while shared state above the steps holds the whole form. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with multi-step forms via routes is storing each step data only in that step component, so going back loses what was entered. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Why give each form step its own route?

Section 48 — Dashboard routing case study

A dashboard routing case study combines layouts, nested routes, guards, and parameters: a layout shell, guarded sections, and detail routes for items. That definition is worth unpacking slowly, because dashboard routing case study shows up in almost every React screen you will build. When you can say in plain words what dashboard routing case study does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It shows how the chapter ideas fit together in a realistic app, rather than as isolated techniques.

Working with dashboard routing case study usually follows a small, repeatable pattern. First, wrap the dashboard in a guarded layout route. Next, nest overview, list, and detail routes inside it. Finally, use parameters for detail routes and search parameters for filters. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function DashboardApp() {
  return <div><Layout /><Outlet /></div>;
}

Read the example from the top down. DashboardApp is the shell, and its nested routes fill the outlet with overview, list, or detail views. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with dashboard routing case study is building a dashboard as one flat route with all views crammed into a single component. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: What routing pieces does a dashboard combine?

Section 49 — Common routing mistakes

Common routing mistakes include plain anchors for internal links, missing server fallback, unprotected routes, and catch-alls that swallow real routes. That definition is worth unpacking slowly, because common routing mistakes shows up in almost every React screen you will build. When you can say in plain words what common routing mistakes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Each mistake breaks a different promise of routing: fast navigation, working refresh, access control, or correct view matching.

Working with common routing mistakes usually follows a small, repeatable pattern. First, use router links for internal navigation. Next, configure the server fallback. Finally, guard protected routes and place catch-alls last. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

function Nav() {
  return <Link to="/home">Home</Link>;
}

Read the example from the top down. This correct link is the contrast case: internal navigation without a reload, with state preserved. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with common routing mistakes is fixing a routing symptom, such as adding a reload, instead of fixing the link or fallback that caused it. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: Name three common routing mistakes.

Section 50 — Putting routing together

A small complete example ties routing together: a layout route, a home route, a dynamic product route, a guarded dashboard, and a catch-all. That definition is worth unpacking slowly, because routing together shows up in almost every React screen you will build. When you can say in plain words what routing together does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Seeing the full table shows how routes divide labour: the layout frames, parameters select, guards protect, and the catch-all guides.

Working with routing together usually follows a small, repeatable pattern. First, define the layout and its outlet. Next, add home, product, and dashboard routes. Finally, add a catch-all last for unknown addresses. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.

Here is a short example you can read line by line:

const routes = [{ path: "/", element: Layout, children: [{ index: true, element: Home }, { path: "products/:id", element: Product }] }];

Read the example from the top down. This table is a miniature app: a layout, a default home view, and a dynamic product view inside it. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.

A common mistake with routing together is leaving the catch-all out, so an unknown address renders nothing at all. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.

To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.

Check: In the final example, which route is the default, and which one uses a parameter?

Practical task

Build a small shop app with routing: a layout with header navigation, a home route, a products list route, a dynamic product detail route that reads its id from the address, a guarded checkout route that redirects to a sign-in view when not signed in, and a catch-all not-found route. Use router links for internal navigation, add search parameters for sorting the product list, and test refresh on a deep product address, back and forward navigation, and the guard redirect.

Chapter questions

  1. Explain the difference between a single-page app and a multi-page app.
  2. Why does a single-page app need server fallback configuration for deep links?
  3. Write a route table with a layout, an index route, a dynamic route, and a catch-all route.
  4. A protected view is reachable by typing its address, even though its link is hidden. What is wrong, and how do you fix it?
  5. When should list filters live in search parameters, and when should they live in local state?