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

  1. What problem a router solves
  2. Why React doesn't ship with routing
  3. How a single-page application works
  4. The browser history API: pushState and popstate
  5. What the application gains with real URLs
  6. What React Router is and its place in the ecosystem
  7. The three ways of using React Router v7
  8. Module vocabulary
  9. Browser history vs. hash history
  10. CicloUrbano's route map

  1. 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-002 still has https://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 useState resets 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 useState turns 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.

  1. 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.

  1. 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.

  1. The browser history API: pushState and popstate

React 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:

  • pushState changes 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.
  • popstate only fires on back/forward, not on pushState. That's why a hand-written router needs to intercept clicks in addition to listening for popstate.
  • The state you pass to pushState gets serialised and survives reloads within the same session. React Router uses this for the navigation state option, 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.

  1. 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-003 and 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-02 returns 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 /taller is 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.

  1. 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-02 comes from the existence of a file app/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.

  1. 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. errorElement for handling errors per route branch (06-03), useBlocker to stop the user from leaving a half-filled form and ScrollRestoration (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 handle and useMatches.
  • It separates configuration from the interface. The map lives in src/routes.jsx and 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.

  1. 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).

  1. 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.

  1. 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-001bici-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:

  • Layout is the root route, not a component that wraps App. This way Header and Footer are 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. /taller stays /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 component." Decide on the mode when you start the project and don't mix them. In this course: data mode, always.

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:

const [section, setSection] = useState(window.location.hash.slice(1) || 'catalogue');

Answer, with reasoning:

  1. Which problem from the list in section 1 does this change fix?
  2. Which ones are still unresolved?
  3. 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:

  1. View the detail page for bike bici-005.
  2. View only cargo-type (carga) bikes in the catalogue.
  3. View the incidents for station est-02, within its detail screen.
  4. Show "Page not found" when someone types /estacionez.
  5. 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:

  1. CicloUrbano as you're about to build it: an SPA with Vite, deployed on Netlify.
  2. An internal panel distributed as a folder of files that operators open from a network drive by double-clicking index.html.
  3. An existing React Router v6 application with 40 routes, to which two new screens need to be added next week.
  4. A new public CicloUrbano website, with a catalogue indexable by search engines and server-side rendering.

Solutions

Solution 1

  1. It fixes the reload issue and, partly, bookmarks: if the user is on #estaciones and 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.
  2. 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.
  3. At a minimum two things are needed: a useEffect that subscribes a handler to window.addEventListener('popstate', …) — with its matching cleanup, as in 05-02 — to sync state when the user navigates back, and making every section change go through history.pushState instead of a bare setSection. 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

  1. 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 use errorElement, useBlocker and ScrollRestoration. Netlify lets you configure the rewrite to index.html with a one-line _redirects file.
  2. Folder opened from disk (file://): createHashRouter, no question. There's no server that can rewrite anything, and with createBrowserRouter the very first reload would break the application. The mode can still be data mode: createHashRouter is data mode too.
  3. 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 gain useBlocker isn'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.
  4. 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

Module 2: React Components

Module 3: Working with Events

Module 4: Advanced Component Concepts

Module 5: React Hooks

Module 6: Routing in React

Module 7: State Management

Module 8: Performance Optimization

Module 9: Testing React Applications

Module 10: Advanced Topics

Module 11: Project: Building a Complete Application

© Copyright 2026. All rights reserved