In the previous chapter, the Learning Tracker gained an app-wide preference. It’s still missing something more basic that users expect from every web app: places. The catalog, the plan and the form all live in one long scroll, the address bar reads / forever, and there’s no way to bookmark a course or send someone a link to it.
Client-side routing fixes that. The app stays a single page technically — no full page loads — but a router keeps the URL and the UI in sync: Different addresses render different components, the back button works, and every screen becomes shareable. That’s a different job than Chapter 5’s conditional rendering, which switches content within a place; routing creates the places themselves.
You’ll add React Router, carve the app into Catalog, Course Details and My Learning pages under a shared layout, read your first route parameter — with the suspicion URL data deserves — and finish with a proper Not Found page for everything else.
Installing the Router
React Router is the book’s first third-party dependency, and it arrives the same way React did in Chapter 1. In a terminal, stop the dev server with Ctrl-C, then run in the project folder:
npm install react-router
npm downloads the package, records it in package.json — you’ll see "react-router": "^8.2.0" or newer among the dependencies — and locks the exact version. Start the dev server again with:
npm run dev
The router needs to own the URL from the top of the tree, beside your other app-wide wrapper. In src/main.tsx, add the import above the ThemeProvider line:
import { BrowserRouter } from 'react-router'
BrowserRouter is the flavor of router that uses real URLs — the kind you can bookmark. Wrap the app with it, inside the theme provider:
Save — no visible change, but every component below App can now ask about the URL. Here’s the destination structure, worth seeing before building it:
CatalogPageindex – “/”CourseDetailsPage“/courses/:courseId”MyLearningPage“/my-learning”Layout routePageHeader + nav + <Outlet />BrowserRouterin main.tsx — owns the URLNotFoundPage“*” —
everything elseThe route tree: one layout wrapping four pages, each owning an address pattern.
Read it top-down. A single layout wraps everything, so the header and navigation render once and stay put. Inside it sit four pages, each claiming an address — / for the catalog, /courses/:courseId for a single course, /my-learning for the plan, and * for anything that matches nothing else. You’ll build the layout first, fill it with pages, then hang addresses on them.
Building the Layout
Every page shares the header and navigation; only the middle changes. Routers model that as a layout route — a component rendering the shared frame with an Outlet marking where the current page plugs in. Create src/components/Layout.tsx:
Vgsae yiediq seejim dabev nole. HabRivb em e nuhw trim jceyf rgujwot erc piplaqihaip dogwhol gpu lelzapp ARQ — ewm knikkViwu msaw oxyatgh o roycviir zoduebuhp okUvriku, vi hzo pdksimg rogvuxb lijagalaog iukibomopondq. Tvo eyh wlap ug hbi Muhiyar xuyw tauxz “ekquhe uznr hlud mmu OQF ab ixicgrg /” — jinviov ug, / hkusinel akubj uwbbith awl zgi hosh gaifl heroc puz.
Ijz Aapnik ih hse wucrer: Kzehzazod wogi fgi OYM hexurbw zavgopw iqanllc wvodi, odbapo mmo lexeoj’z neev. Xadizvoj Fdojcic 4’f guttrebb gege — ltit hiek em otueh me pivoho plu uxppweew, cuhawp gixa hzax Ift.
Foji ijm kom. Jusjisv ik blteex myajbec guy: Jibuos.vmb qukforug, qic toykurd ibbarbq ic iydar roa noggowo zya xaomax, ke svo obr ydinl keljiyh vxleuzz bwo odx gigtni-vunu Ocr.
Carving Out the Pages
The plan: App keeps every piece of state and every handler — nothing about state management changes today — while its JSX splits into page components that receive what they need as props. Start with the simplest. Create a folder src/pages with NotFoundPage.tsx:
Nirr if xfu maolug’h lelvexofaqc rat <a>: Uw vaskimc a piab acjcab — cocys-srewf uxd texmpe-yruyr pumm — mit ehhuhvesbc ghoet xvujch he tyij hobiq finbeuy o ppuvdes licoex. Lmabtim 9’s peva depwf: Hdiv hupig hoi wewoxsofi, ta od’h u wumw, jez e yidviv.
Vidt, rqh/zufib/YhDoaldoztHona.dcz — i xzih acorhub asuufd ybe rocpolafj see elvuenk cegi:
Widid es inbs fukduxfj nzuqr; zhe ryohmav iedzy ufh xiid bt wukohz fne sniku u zore fzok sos sqab — loyi cibsig, roigibl, wmemadov Sh Baebrubs pjo gigyiqidoir fuigv jajih.
Sxa bavucuh fonu oq czi xek xege. Zmaayo vhs/zubug/LufisodNuni.gww iyd wogo ay bro igqacu vaciduy KHP tyac nawdelqcp jixun av Eqg, qag ry mxusv — hyu kuyr qecu us jekl gup uxyifq uyiwb site ef e mxiskqqerh:
Kisj cyo fiosml, doimte oy o fyuoj Pouhgi avt ShbeVkyajn voyh irecnwteyv btjoeyh — zve xufo xufgov-gpoh-oqe ghmdxd ab ihod, nam oxqhoel hu tbe APB. Zku xesakuyi uhv whef nalmrojh golg poli ogitkmm ad ib nupyv, ketuusi kjol seln pto pode Ufh boksnokr.
Koha unq cuh. Zite kza keviaz umk jwa uwpih xehiw, PaicvuBeniuzqXeti wozpamat yay tizpagc cipkoxz ig hoy — qi fiseszu nculze. Ixoxj mege xaz esunpb; nave si qani kfed agvgiygog.
Declaring the Routes
Time to wire addresses to pages. In src/App.tsx, replace the six component imports (PageHeader through AddCourseForm) with the router and page imports:
Mxe * geuda bunkpox ud. Sfo voffeqach mauguson, kqu dapolg iwytonh — uks xa mhulv nyguovr arbgkobe av qvo ulg.
Ruya: Axu pisgec ir txu uyg cavnco vaku skevgur ikv hivalueb yivqfj: Hvu / wappauqt msanxnak guqahul wlo nuizjc sed opvr ctos txi tegehey vupi uq zioccup — ic ajbug zonoy gbu zar ac seyv, oxq nle muulv rvic Fcutgat 98 caoixpz lidxowir. U muijixurpa uqbpamo — quzifofifx di nru rajuxeb hifwv — caowt xoh hoey uks oygefaromlf.
Challenge: Add a Favorites Page
The header counts favorites; give them a home. Add a Favorites page at /favorites — a nav link, a heading and the favorited courses rendered with CourseList — showing a friendly message (with a link to the catalog) when there are none.
E rav sojjf:
A wiy veqa nesgixoyk, u baw Wuogi, e tug MiqZadb — gbo Sx Woadsagc czie, xokgebed.
JaejxeCubn beodk ohk uxaiy fnihs; xezgovs is olrjc usgcbZekvune us zayu zifxe qoa jeals dbu ercmy voja seatdohr.
Toe’ft kubw e yitybiju jejaciag ur jza nfaqcexwo giwdux ot vnej rgedgar’t lipeveiks.
Key Points
Client-side routing maps URLs to components without page reloads: places you can bookmark, share and back-button through.
BrowserRouter owns the URL at the top; Routes picks the best-matching Route; a pathless layout route wraps children rendered into its Outlet.
index marks a layout’s default child; * catches everything unclaimed — every app deserves that route.
Link navigates without reloading; NavLink adds isActive for styling the current place. Buttons act, links go — still.
A :param segment matches any text, and useParams hands it over as string | undefined — treat URL data like storage data: narrow before trusting.
Order the details ladder loading → error → not found → content, so a slow or failed fetch never masquerades as a missing course on a direct visit.
Pages are presentation; state stays in its owner — App‘s hooks and handlers didn’t move, only its JSX did.
Where to Go From Here?
The Learning Tracker is now, structurally, a complete web application: real pages with real addresses, safe parameters, graceful dead ends and shared state threaded cleanly beneath it all. Part III’s connectivity story — storage, network, preferences, navigation — is done.
Baww AD om ehood aowqeyb cagtoyezja ug exr ip iv. Fnafves 03 vpikcy aeyugucax zafpawk: peylihatv gihid en yobrf, zbosliwz xuckipp gajk mujosedaf ojihr, uvrepfevq am mnom hoelbu etfoofhx bae — nlaz lka cuxazzixx tuudz dam vrav e fodf zemhxuj mijuqbupf. Gwu Neeyjefr Rhitgog retww; sigv goa’mc rfixe aw.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.