Module 5 ended on an uncomfortable diagnosis: CicloUrbano works, but it's a single screen. The Header you wrote in module 2 has carried a «Catalogue · Stations · My bookings» menu ever since, with three links that go nowhere — they're decorative <a href="#catalogo"> tags — and that debt couldn't be settled because the piece that ties the browser URL to which component gets painted was missing. That piece doesn't ship with React: you have to add it yourself. In this lesson you'll understand what client-side routing is, how a single-page application works under the hood, what CicloUrbano gains once every screen has its own address, and which of the three ways of using React Router v7 you'll use for the rest of the course. You won't write the configuration yet — that's the next lesson — here you build the mental model and the vocabulary you'll need so you don't get lost in it.
Contents
- What problem a router solves
- Why React doesn't ship with routing
- How a single-page application works
- The browser history API:
pushStateandpopstate - What the application gains with real URLs
- What React Router is and its place in the ecosystem
- The three ways of using React Router v7
- Module vocabulary
- Browser history vs. hash history
- CicloUrbano's route map
- What problem a router solves
Let's start with the project's current state. If you wanted to show three different sections in CicloUrbano today without a router, you'd do something like this:
// src/App.jsx — "hand-rolled" routing, what you will NOT be maintaining
import { useState } from 'react';
import Layout from './components/Layout.jsx';
import CataloguePanel from './components/CataloguePanel.jsx';
import StationsPanel from './components/StationsPanel.jsx';
import BookingsPanel from './components/BookingsPanel.jsx';
function App() {
const [section, setSection] = useState('catalogue');
return (
<Layout onSectionChange={setSection}>
{section === 'catalogue' && <CataloguePanel />}
{section === 'stations' && <StationsPanel />}
{section === 'bookings' && <BookingsPanel />}
</Layout>
);
}
export default App;It works. And yet it's a poor solution, for reasons that have nothing to do with code style:
- The URL never changes. A user looking at the detail page for
bici-002still hashttps://ciclourbano.test/in the address bar. They can't send that link to anyone. - The back button leaves the application. The browser has no idea the user has "navigated" three times; as far as it's concerned, nothing has happened since the page loaded. Pressing back sends them to Google.
- Reloading (F5) returns to the start. The
useStateresets and goes back to the catalogue, no matter where the user was. - There are no bookmarks. Saving a screen to favourites always saves the home page.
- There's no internal history. You can't go "back" within the application itself.
- And it scales terribly. With nine screens and parameters (which bike? which station? which tab?), that
useStateturns into an object with five fields and an unreadable chain of conditionals.
A router is the piece that keeps two things in sync: the URL the user sees and the component tree React paints. In one direction, changing the URL changes what's displayed; in the other, navigating inside the application changes the URL.
That two-way relationship is the key. A router isn't "a fancier switch statement": it's the decision to turn the URL into application state — state that lives outside React, that the browser already knows how to manage, and that the user can edit, copy and share.
- Why React doesn't ship with routing
It's a fair question: Angular ships with its router out of the box, Vue has an official one. Why doesn't React?
The answer is in what React says about itself since lesson 01-01: it's a library for building user interfaces, not an application framework. Its scope ends at "given a state, produce a tree of elements and sync it with the DOM." Everything else — routing, HTTP requests, global state management, internationalisation — is deliberately left out.
That decision has practical consequences:
| Consequence | Detail |
|---|---|
| React works outside the browser | React Native (10-05) has no URLs. A router built on window.history wouldn't make sense there. |
| The ecosystem competes | React Router, TanStack Router and others have evolved in parallel; the best ideas from one end up in the other. |
| Every project chooses | An application embedded in an admin panel might not need URLs; a store needs them for SEO. |
| The cost is yours | Installing, picking a version, learning an API that isn't in React's own documentation. |
The trade-off is real: you have to choose, and the choice is often wrong because old code you find online uses APIs from three versions ago. That's why section 7 of this lesson matters so much.
- How a single-page application works
Compare the two navigation models. On a traditional (multi-page) website, every click on a link is a full request to the server:
sequenceDiagram
participant U as User
participant N as Browser
participant S as Server
U->>N: Clicks "Stations"
N->>N: Discards the current page (blank screen)
N->>S: GET /stations
S-->>N: stations.html
N->>S: GET styles.css, scripts.js, images…
S-->>N: resources
N->>N: Parses the HTML and paints the page from scratch
N-->>U: New screen (all JavaScript state was lost)
In a single-page application (SPA), the server delivers a single index.html the first time, and from then on JavaScript takes care of everything:
sequenceDiagram
participant U as User
participant R as React Router
participant React
participant H as history (browser)
U->>R: Clicks a <Link to="/estaciones">
R->>R: preventDefault() — stops the browser from navigating
R->>H: history.pushState(null, '', '/estaciones')
Note over H: The address bar changes,<br/>NO request is sent to the server
R->>R: Matches '/estaciones' against the route map
R->>React: Renders <StationsPage />
React->>React: Reconciliation (01-05): replaces only what changed
React-->>U: New screen, no reload, state intact
The differences that matter:
| Aspect | Traditional multi-page | Single-page application |
|---|---|---|
| Requests per navigation | HTML + all resources | None (or just JSON data) |
| Flicker between screens | Yes, blank screen | No |
| JavaScript state | Lost on every navigation | Preserved |
| First load | Fast (HTML already assembled) | Slower (the JS bundle has to download) |
| Who decides what's shown | The server, based on the route requested | The router, on the client |
| SEO by default | Good | Needs care (or SSR, 10-01) |
Notice the detail that makes the whole trick possible: preventDefault(). A plain <a href="/estaciones"> causes the browser to tear down the whole application and request the page from the server. A React Router <Link to="/estaciones"> renders a real <a> — with its href, so it can be opened in a new tab and so search engines can follow it — but intercepts the click and turns it into an internal state change. This distinction comes back in 06-02, and it's the cause of the module's most common bug.
- The browser history API:
pushState and popstate
pushState and popstateReact Router doesn't invent anything magical: it relies on a standard browser API that's been around since HTML5. It's worth seeing it raw once, because understanding it clears up almost every question that follows.
// Teaching example in plain JavaScript (not part of CicloUrbano)
// 1. Change the URL WITHOUT reloading, adding an entry to history
history.pushState({ screen: 'stations' }, '', '/estaciones');
// The address bar now reads /estaciones.
// The server has NOT been told anything. The DOM hasn't changed either:
// painting the new screen is YOUR responsibility.
// 2. Change the URL WITHOUT adding an entry (replaces the current one)
history.replaceState({ screen: 'signin' }, '', '/acceso');
// Useful after signing in: you don't want "back" to return to the form.
// 3. Find out that the user pressed back or forward
window.addEventListener('popstate', (event) => {
console.log('The user navigated to:', location.pathname);
console.log('Associated state:', event.state); // { screen: 'stations' }
paintScreen(location.pathname); // again, your job
});Three observations worth committing to memory:
pushStatechanges the URL but paints nothing. The browser doesn't trigger any reload or event. Your code decides what to show. That "your code" is exactly what React Router saves you from writing.popstateonly fires on back/forward, not onpushState. That's why a hand-written router needs to intercept clicks in addition to listening forpopstate.- The state you pass to
pushStategets serialised and survives reloads within the same session. React Router uses this for the navigationstateoption, which you'll see in 06-04.
If you had to write your own router, you'd need to: intercept clicks, call pushState, listen for popstate, match the path against a set of patterns, extract parameters from dynamic segments, sort matches by specificity, handle nested routes, restore scroll position… That's exactly the work React Router has had done since 2014, and the reason not to do it by hand.
- What the application gains with real URLs
It's worth listing the concrete gains, because they justify the added complexity:
- Shareable links. An operator can send a colleague
https://ciclourbano.test/bicicletas/bici-003and both see the same detail page. Without URLs, the only possible instruction is "go in and search for the Cargo Max." - Back and forward work. The back button is, by a wide margin, the most-used control in any browser. Leaving the application by pressing it is a serious user-experience failure.
- Reloading keeps the place. F5 on
/estaciones/est-02returns to/estaciones/est-02. Without URLs, it returns to the home page. - Bookmarks. The user can save "My bookings" to favourites.
- Search engine indexing. Every screen is a distinct document with its own address, which is a necessary (though not sufficient — the content also needs to be in the HTML, which is where the SSR from 10-01 comes in) condition for a search engine to display it.
- Useful analytics. Measurement tools count page views by URL. With a single URL, all your data says "home page."
- Debuggable state. When a colleague tells you "it's broken," the URL is half the bug report.
- Per-screen code splitting. If
/talleris its own route, its JavaScript can be downloaded only when someone visits it. This is the lazy loading you'll see in 08-04, and without routes there's nowhere to make the cut.
- What React Router is and its place in the ecosystem
React Router is the oldest and most widely used routing library for React. It translates the current URL into a component tree according to a route map you declare, and provides the components and hooks for navigating, reading parameters and composing nested screens.
Its history explains a good part of the confusion you'll run into:
| Version | Approximate year | Main novelty |
|---|---|---|
| v3 and earlier | 2015–2016 | Routes as static configuration, global browserHistory |
| v4/v5 | 2017–2019 | "Everything is components": <Switch>, order-based matching |
| v6 | 2021 | <Routes>, better matching (order no longer matters), relative routes, hooks |
| v6.4 | 2022 | createBrowserRouter, loader, action: the "data" mode |
| v7 | 2024–2025 | Unified with Remix; three official modes; react-router package |
And it's not alone. It's worth knowing what's around it, even though this course uses React Router:
- TanStack Router. A modern alternative whose strongest argument is type safety: routes, parameters and query parameters typed end-to-end with TypeScript (10-04), with built-in parameter validation. If you work on a large, strict TypeScript project, it's worth a look.
- Framework-integrated routers. Next.js (10-01 and 10-02) ships its own file-system-based router: the route
/estaciones/est-02comes from the existence of a fileapp/estaciones/[estacionId]/page.jsx, without you declaring a map. Remix (today merged into React Router v7) and Astro do the same. When you adopt one of those frameworks, you don't install React Router: you use theirs. - Minimalist routers such as
wouter, a few kilobytes in size, for small applications where React Router would be overkill.
The practical conclusion: React Router is the sensible default choice for an SPA built with Vite, which is exactly CicloUrbano's case, and the concepts you'll learn here (dynamic segments, nesting, outlet, index routes) are the same across every router in the ecosystem, just under different names.
- The three ways of using React Router v7
Here's the main source of confusion for anyone starting today. React Router v7 can be used in three distinct ways, and the documentation calls them modes:
| Mode | How the map is defined | What it enables | When to choose it |
|---|---|---|---|
| Declarative | <BrowserRouter> + <Routes> + <Route> in JSX |
Links, parameters, nesting. No loader/action |
Migrations from v6; small applications; adding routes to an existing app |
| Data | createBrowserRouter([...]) as an object + <RouterProvider> |
Everything above plus loader, action, errorElement, useNavigation, useBlocker, ScrollRestoration |
An SPA with Vite that wants the modern capabilities. This is the course's choice |
| Framework | A routes.ts file + the React Router Vite plugin |
Everything in data mode plus server-side rendering, type generation, automatic code splitting | When React Router acts as a full framework, as an alternative to Next.js |
This course uses data mode: createBrowserRouter + RouterProvider. The reasons:
- It's what the official documentation recommends for a new SPA, and the natural migration path towards framework mode if that's ever needed.
- It unlocks APIs we need.
errorElementfor handling errors per route branch (06-03),useBlockerto stop the user from leaving a half-filled form andScrollRestoration(06-04) only exist in data mode. With declarative mode you'd go without them. - The route map is data, not markup. An array of objects can be traversed, transformed and used to automatically generate a menu or breadcrumbs. You'll take advantage of this in 06-03 with
handleanduseMatches. - It separates configuration from the interface. The map lives in
src/routes.jsxand doesn't get mixed in with the screen.
Very important for reading other people's code: the old form is still valid, and it's what you'll see in the vast majority of existing code, tutorials and forum answers:
// DECLARATIVE mode (v6 legacy). Valid in v7, but NOT what we'll use.
import { BrowserRouter, Routes, Route } from 'react-router';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<CataloguePage />} />
<Route path="/estaciones" element={<StationsPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}And here's the equivalent in the mode you'll actually use, so you can already see the resemblance:
// DATA mode. This is the course's style (you'll set up the full thing in 06-02).
import { createBrowserRouter } from 'react-router';
export const router = createBrowserRouter([
{ path: '/', element: <CataloguePage /> },
{ path: '/estaciones', element: <StationsPage /> },
{ path: '*', element: <NotFoundPage /> }
]);The concepts carry over one to one. path, element, nesting, <Link>, useParams, useNavigate, <Outlet />: they're identical in both modes. The only thing that changes is where you declare the map and how it's mounted at the root. If you land in a v6 project tomorrow, you'll be able to read it without effort. The rule is the same one you already applied with CSS Modules in 02-05: pick one form and be consistent throughout the project; mixing <BrowserRouter> with <RouterProvider> in the same application is a guaranteed bug.
One more note about package names, because it trips everyone up: in v6 you installed react-router-dom; in v7 the package is react-router, and everything comes from it. react-router-dom is still published as a re-export for backwards compatibility, so you'll see both imports in the wild. We'll go over it in detail in 06-02, when you install it.
- Module vocabulary
Pin down these terms now; the rest of the module uses them without redefining them.
| Term | Meaning | Example in CicloUrbano |
|---|---|---|
| Route | A rule that associates a URL pattern with a component | { path: '/estaciones', element: <StationsPage /> } |
| Segment | Each chunk between slashes in a route | In /estaciones/est-02 there are two: estaciones and est-02 |
| Dynamic segment | A variable segment, written with a colon, whose value gets captured | /bicicletas/:bicicletaId captures bici-003 |
| Route parameter (param) | The value captured by a dynamic segment | { bicicletaId: 'bici-003' } via useParams() |
| Nested route | A route that is a child of another, painted inside its parent | incidencias inside /estaciones/:estacionId |
outlet |
The slot where the parent paints its active child | <Outlet /> inside Layout |
| Index route | A child that gets painted when the URL matches the parent exactly | The flota tab at /estaciones/est-02 |
| Wildcard route (splat) | *, matches anything not captured earlier |
The not-found page |
| Link | <Link> or <NavLink>: navigation without a reload |
The three in the Header menu |
| Programmatic navigation | Navigating from code, not from a click on a link | Going to /reservas after confirming a booking (06-04) |
| Query parameters | What follows ?, for filters and sorting |
/?tipo=electrica |
location |
An object with the current URL broken down | { pathname, search, hash, state, key } |
| Match | The route (or chain of routes) the current URL activates | useMatches() in the breadcrumbs |
A detail of judgement that's often forgotten, and that drives a design decision in 06-02: a dynamic segment identifies a resource; a query parameter modifies a view. The detail page for one specific bike is /bicicletas/bici-003 (identity); a catalogue filtered to electric bikes is /?tipo=electrica (modification). If in doubt, ask yourself whether, without that piece of data, the screen still makes sense: without bicicletaId there's no detail page to show (dynamic); without tipo the catalogue is simply shown in full (query).
- Browser history vs. hash history
React Router can manage the URL in two ways, and the choice has a server-side consequence that catches people off guard the first time they deploy.
Browser history (createBrowserRouter) |
Hash history (createHashRouter) |
|
|---|---|---|
| Example URL | https://ciclourbano.test/estaciones/est-02 |
https://ciclourbano.test/#/estaciones/est-02 |
| API it uses | history.pushState |
The URL's # fragment |
| Appearance | Clean, indistinguishable from a traditional website | Has a hash mark, gives it away |
| SEO | Correct | Poor: the fragment never reaches the server |
| Server requirement | Yes: rewrite to index.html |
No: works with plain static hosting |
The server requirement is the crux of it. Imagine the user is on /estaciones/est-02 and presses F5. This time the browser genuinely does request /estaciones/est-02 from the server, because a reload isn't controlled by JavaScript. If the server has one file per route, it won't find anything and will return a 404, even though the route worked perfectly when navigating through links inside the app.
The fix is to configure the server so that any unknown route returns index.html, letting the client-side router resolve it from there. It's a single line of configuration on any modern host (Netlify, Vercel, Nginx, Apache), and Vite's dev server already does this by default, which is why this problem doesn't show up until deployment day. We'll cover it in detail in 11-05.
flowchart TD
F5["User reloads /estaciones/est-02"] --> SRV{"Does the server have<br/>that file?"}
SRV -->|"Not configured"| E404["404 Not Found 💥<br/>the app doesn't even start"]
SRV -->|"With rewrite to index.html"| OK["Returns index.html<br/>→ React boots<br/>→ the router reads the URL<br/>→ paints StationDetailPage ✅"]
style E404 fill:#fecaca
style OK fill:#dcfce7
When to use createHashRouter, then:
- You're publishing to static hosting that doesn't allow configuring rewrites (the classic case: GitHub Pages without workarounds).
- You're shipping the application as a standalone file, an internal CD-ROM or a
file://link. - You're embedding it in a legacy system whose server you don't control.
In every other case, createBrowserRouter, which is what CicloUrbano uses. The API is identical apart from the function name, so switching between the two is a one-line change.
- CicloUrbano's route map
This is the module's destination. You'll implement it in 06-02 and 06-03, and honour it for the rest of the course:
| Route | Screen | Notes |
|---|---|---|
/ |
Bike catalogue | Accepts ?tipo= for the TypeSelector filter |
/bicicletas/:bicicletaId |
Bike detail page | Dynamic segment: bici-001 … bici-005 |
/estaciones |
Station list | The three StationCard cards |
/estaciones/:estacionId |
Station detail | With nested tabs |
/estaciones/:estacionId (index) |
Fleet tab | Index route: shown by default |
/estaciones/:estacionId/incidencias |
Incidents tab | Sibling nested route |
/reservas |
My bookings | Reads from BookingsProvider |
/reservas/nueva |
Booking form | Redirects to /reservas on confirmation (06-04) |
/acceso |
Mock sign-in | Choose between usr-01 and usr-02 |
/taller |
Operator panel | Protected: operario role only (06-05) |
* |
Not found | Any URL that doesn't match |
And here's the tree, already including the nesting you'll build in 06-03:
flowchart TD
RAIZ["/ · Layout<br/>(Header + Outlet + Footer)"]
RAIZ --> IDX["index · CataloguePage"]
RAIZ --> BICI["bicicletas/:bicicletaId<br/>BikeDetailPage"]
RAIZ --> EST["estaciones<br/>StationsPage"]
RAIZ --> DET["estaciones/:estacionId<br/>StationDetailPage"]
DET --> FLO["index · FleetTab"]
DET --> INC["incidencias · IncidentsTab"]
RAIZ --> RES["reservas<br/>BookingsPage"]
RAIZ --> NUE["reservas/nueva<br/>NewBookingPage"]
RAIZ --> ACC["acceso<br/>SignInPage"]
RAIZ --> PROT["(no path) ProtectedRoute 🔒"]
PROT --> TAL["taller · WorkshopPage"]
RAIZ --> NF["* · NotFoundPage"]
style PROT fill:#fde68a
style TAL fill:#fde68a
Notice three decisions that will be justified in due course:
Layoutis the root route, not a component that wrapsApp. This wayHeaderandFooterare neither unmounted nor remounted when the screen changes (06-03).- The catalogue is an index route, not a repeated
path: '/'. It's the screen shown when the URL matches the parent and nothing more. - The protected branch is a route with no
path: it groups screens under a guard without adding any segment to the URL./tallerstays/taller, not/protegido/taller(06-05).
Common Mistakes and Tips
Thinking React Router makes requests to the server. It doesn't. Changing routes with <Link> is a purely client-side operation: the URL updates and React repaints. The data still comes from wherever it came from (your domain.js, a fetch, a loader). If moving from / to /estaciones makes you expect the server to send fresh HTML, your mental model is wrong.
Confusing declarative mode with data mode and mixing their APIs. It's the number-one mistake today. You copy one example with <BrowserRouter>, another with createBrowserRouter, and end up with useNavigate throwing "useNavigate() may be used only in the context of a
Looking in React's documentation for what's in React Router's. There's not a single mention of <Link> on react.dev, and it's not an oversight: they're different projects, with different versions and release schedules. Keep both references handy.
Installing react-router-dom in v7 out of habit. It works for backwards compatibility, but the correct package is react-router. Mix both imports in the same project and you can end up with two copies of the router and baffling context errors.
Putting things in the URL that shouldn't be there. The URL is public, gets shared and gets saved in history. A catalogue filter or a page number belong there; a session token, an email address or the contents of a half-filled form don't.
Tip: sketch the route map before you write it. Drawing the screens and their addresses on paper, like the table in section 10, saves expensive restructuring later. The URL is a public interface of your application: change /bicis/:id to /bicicletas/:bicicletaId tomorrow and you break the links people have already saved.
Tip: use descriptive parameter names. :bicicletaId and :estacionId instead of :id and :id. When you read the parameters of a nested route in 06-03 you'll have several at once, and two different ids is a problem; besides, the code documents itself.
Exercises
Exercise 1: diagnose the hand-rolled routing
Look again at the App component from section 1, the one that uses useState to pick a section. A colleague suggests "improving" it by reading the URL hash on startup:
Answer, with reasoning:
- Which problem from the list in section 1 does this change fix?
- Which ones are still unresolved?
- What's the bare minimum needed for the back button to work?
Exercise 2: classify the route map
For each of these CicloUrbano needs, decide whether it corresponds to a dynamic segment, a query parameter, a nested route or a wildcard route, and write the resulting URL:
- View the detail page for bike
bici-005. - View only cargo-type (
carga) bikes in the catalogue. - View the incidents for station
est-02, within its detail screen. - Show "Page not found" when someone types
/estacionez. - Sort the station list by number of docks, highest to lowest.
Exercise 3: choosing a mode and history type
For each scenario, say which React Router v7 mode (declarative, data, framework) and which router creator (createBrowserRouter or createHashRouter) you'd use, and why:
- CicloUrbano as you're about to build it: an SPA with Vite, deployed on Netlify.
- An internal panel distributed as a folder of files that operators open from a network drive by double-clicking
index.html. - An existing React Router v6 application with 40 routes, to which two new screens need to be added next week.
- A new public CicloUrbano website, with a catalogue indexable by search engines and server-side rendering.
Solutions
Solution 1
- It fixes the reload issue and, partly, bookmarks: if the user is on
#estacionesand presses F5, the app starts on the right section, because the hash is preserved and the initial state is computed from it. They could also save the URL as a bookmark. - Still unresolved: the back button (nobody's listening for
popstate, so going back changes the hash but React never finds out and the screen doesn't change); shareable links with parameters (which bike? doesn't fit in a single hash without inventing a format); the growth of the conditional chain; and search-engine indexing, because the hash never reaches the server. - At a minimum two things are needed: a
useEffectthat subscribes a handler towindow.addEventListener('popstate', …)— with its matching cleanup, as in 05-02 — to sync state when the user navigates back, and making every section change go throughhistory.pushStateinstead of a baresetSection. As soon as you write those two pieces you'll have started building your own router, which is exactly what you shouldn't do.
Solution 2
| Need | Type | URL |
|---|---|---|
1. Detail page for bici-005 |
Dynamic segment | /bicicletas/bici-005 |
| 2. Catalogue filtered to cargo bikes | Query parameter | /?tipo=carga |
3. Incidents for est-02 |
Nested route (with a dynamic segment in the parent) | /estaciones/est-02/incidencias |
| 4. Nonexistent URL | Wildcard route | /estacionez → matches * |
| 5. Stations sorted by docks | Query parameter | /estaciones?orden=plazas&sentido=desc |
The criterion in cases 2 and 5 is the one from section 8: the filter and the sort order modify a view that exists just fine without them, so they go in the query string. In case 1, without bici-005 there's no possible detail page: it's identity, and it goes in the path.
Solution 3
- CicloUrbano on Netlify: data mode with
createBrowserRouter. It's a new SPA with no server constraints, it wants clean URLs and it's going to useerrorElement,useBlockerandScrollRestoration. Netlify lets you configure the rewrite toindex.htmlwith a one-line_redirectsfile. - Folder opened from disk (
file://):createHashRouter, no question. There's no server that can rewrite anything, and withcreateBrowserRouterthe very first reload would break the application. The mode can still be data mode:createHashRouteris data mode too. - v6 application with 40 routes: keep the declarative mode it already has and add the two new screens as two more
<Route>elements. Rewriting 40 routes just to gainuseBlockerisn't worth it; if the team decides to migrate, let that be its own project, not a side effect of a delivery. Consistency over modernity. - Public website with SEO and server-side rendering: React Router v7's framework mode, or Next.js (10-01) directly. As soon as you need HTML generated on the server, routing stops being client-only and the framework needs to handle both sides. Here you don't even choose between browser history and hash history: hash is ruled out because it never reaches the server.
Conclusion
Client-side routing solves the problem module 5 ended on: making the URL and the interface correspond to each other. You've seen that a single-page application delivers a single index.html, and that from there, navigation consists of intercepting clicks, calling history.pushState to change the address without reloading, and letting the router decide which component to paint, with the reconciliation from 01-05 taking care of touching only what changed. In exchange for that complexity, CicloUrbano gains shareable links, working back and forward buttons, reloads that land in the right place, bookmarks, per-screen analytics, indexing, and the option to load each section's code only when it's needed.
You've also settled the decisions that govern the rest of the module. React doesn't ship with a router because it's an interface library, not an application framework, and the ecosystem offers React Router, TanStack Router and the routers built into Next.js or Remix. Of the three ways to use React Router v7 — declarative, data and framework — the course uses data mode, with createBrowserRouter and RouterProvider, because it's the recommended choice for an SPA with Vite and the only one that enables errorElement, useBlocker and ScrollRestoration; the classic v6 <BrowserRouter><Routes><Route> is still valid, it's what you'll see in a huge amount of existing code, and every concept carries over unchanged. You have the module's vocabulary — route, dynamic segment, nested route, outlet, index route, link, programmatic navigation, query parameters — you know why createBrowserRouter requires the server to rewrite to index.html and in which rare cases createHashRouter is needed, and you have the complete CicloUrbano route map in front of you.
Time to build it. In the next lesson you'll install React Router, create src/routes.jsx with the map, mount <RouterProvider /> in main.jsx respecting the existing provider order, reorganise the project with a src/pages/ folder, finally replace the Header's <a href="#…"> links with <NavLink> and an active class, read the bike identifier with useParams, and put the TypeSelector filter into the URL with useSearchParams. The next lesson is Setting Up React Router.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
