Routing, login and protected pages. A house has rooms with their own doors, and some doors are for the staff only.
Theme
React Router, auth, guards
Le Bistro today
Pages, a dish page, login
Your project
v0.4 · pages and a login
Length
2 hours · one break
la maison“rooms and doors”
Le Bistro · L30 · 120 minutes
Today's menu
AccueilHello where Le Bistro is today3′
ApéritifQuestions and two homework demos8′
EntréeProblem 1 send me the link5′
PlatReact Router pages, layouts, params24′
AtelierClasswork 1 your pages15′
Pause caféBreak music on, screens off10′
Second platProblem 2, login and guards staff only27′
AtelierClasswork 2 with AI prompt it, then check it15′
MasterclassOne prompt, the whole Le Bistro CRAFT, then the short version8′
DessertWrap-up recap, homework, next time5′
Route Lecture 4 of 5
Le Bistro, lecture by lecture
L27 · done
Components
Home page and menu from components and a data file.
L28 · done
State
Add to cart works. Chips filter, search finds.
L29 · done
Effects
The real menu from the API. A cart that survives a refresh.
L30 · today
Routing
Pages with their own address. Login and staff-only doors.
L31
Context
One cart and one session for the whole site.
Your projectv0.1 ✓catalog from a data filev0.2 ✓cart, filters, searchv0.3 ✓real API, saved cartv0.4 · todaypages, login, guardsv0.5context and reducer
Today's goal Press next to unveil it
From one long page to a house of rooms
Under the blind: Coq au Vin on its own page, at its own address.
/menu/5 opens Coq au Vin, Back works
/menu?category=drinks can be shared
A login; /account and /kitchen are locked
After today
Apéritif Before we start
Questions from last time?
Effects, dependencies, cleanups, custom hooks.
01
When does useEffect(fn, [category]) run?
After the first render, and after every render in which category changed.
02
What is a cleanup for?
To cancel what the effect started: abort a request, clear a timer, remove a listener. It runs before the next effect and at the end.
03
Do two components calling useApi share data?
No. Hooks share logic, not state. Each caller gets its own.
Homework check Two volunteers, two minutes each
Show us your v0.3
Open DevTools → Network. Click a category, type a search, press F5.
Look for
Products from DummyJSON, no data file
A skeleton while loading, a message offline
One request per search, not per letter
The cart survives F5
No missing-dependency warnings
Entrée · Problem 1 Before the new tool
“Send me the link to Coq au Vin.”
A friend wants to see the dish before dinner. Our whole site lives at one address.
Problem 1 Try it
One address for everything
localhost:5173/
Copy the link
You get localhost:5173/: the home page, not the dish.
Press Back
You leave Le Bistro instead of going back to the menu.
Refresh
You land on the home page again, not where you were.
Problem 1 Your turn
What would we need?
An address for every screenBack and Forward that workChange pages without reloadingThat is routing
Plat · Solution 1 Type an address and press Enter
The router reads the address, picks the page
Result
Press Go.
Routes are checked against the address. :id matches any one segment and hands it to the page as a param. * catches everything else: the 404 page.
Solution 1 Click the links in the frame
One frame, many pages: <Outlet />
Le Bistroby IT Step
Le BistroOpen daily 12–23
The dashed hole
Header and Footer never change. <Outlet /> is where the current page goes.
Solution 1 · Recipe Type it with me
Install React Router and wrap the app
Install itOne package: react-router (version 8).
Wrap the app in BrowserRouterIt watches the address bar for the whole app.src/main.tsx
Make a pages folderHomePage (Hero + Favourites), MenuPage (the old MenuSection).src/pages/
Solution 1 · Recipe The map of the house
The routes in App
Routes and a layout routeThe layout wraps every page inside it.
The index pageindex = the address of the parent: /.
PathsNo leading slash inside a parent: menu, menu/:id.
The catch-all* matches anything else: the 404 page.
SiteLayout with an OutletHeader, the hole, Footer.src/layouts/SiteLayout.tsx
The pages still need the cart.
In the real App every page gets cart, onAdd, onChangeQty as props (see code/after-L30/src/App.tsx). Remember how many: that's the problem of L31.
Solution 1 Click both, and watch the cart
<Link>, not <a href>
<a href="/menu">
localhost:5173/
The browser loads the whole site again: a white flash, and all state in memory starts over.
<Link to="/menu">
localhost:5173/
The router changes the address and swaps the page. No reload, state kept.
Solution 1 · Recipe A page with a param
Coq au Vin at /menu/5
Read the paramuseParams() gives { id: "5" }. A string, always.src/pages/DishPage.tsx
Find the dishThe API has no "one dish" endpoint: load the menu, find by Number(id).
Loading, error, not foundA wrong id like /menu/999 shows the 404 page.
Link the cards to itThe photo and the name in DishCard become Links.DishCard.tsx
Solution 1 Click the chips, then Back
Filters in the address: useSearchParams
Where does this state live?
In the URL. It survives F5, it can be shared, and Back undoes it. useState for category and query is gone.
BrowserF5 on /menu/5
Vercellooks for a file /menu/5
404no such file
vercel.json rewriteanything else → /index.html
React Routerreads /menu/5 → DishPage
Solution 1 One file fixes it
Refresh on Vercel: 404
The first rule is the production twin of Vite's proxy: the deployed site asks Vercel for /api/…, Vercel asks the API.
Solution 1 The errors you will see today
When routing goes wrong
A Link outside the router
<App /> without <BrowserRouter> in main.tsx
useHref() may be used only in the context of a <Router> component.
<BrowserRouter><App /></BrowserRouter>
The layout forgot its Outlet
<Header /><main></main><Footer />
Header and Footer appear, every page is empty.
<main><Outlet /></main>
A param compared as a number
data.find((d) => d.id === id)
Always "not found": 5 === "5" is false.
data.find((d) => d.id === Number(id))
The old import
import { Link } from "react-router-dom";
Cannot find module 'react-router-dom'
import { Link } from "react-router";
Quick check The router's eyes
Which page opens /menu/drinks?
menu/:id matches any one segment after /menu. DishPage then can't find a dish with that id and shows the 404 page itself. Filters belong in the query: /menu?category=drinks.
Atelier · Classwork 1 15 minutes, on your own project
Your pages
Baseeveryone
Routes and a layout
npm install react-router, <BrowserRouter> in main.tsx.
A layout with Header, <Outlet />, Footer.
Routes: / (home), /products, * (404). NavLinks in the Header.
Plusmost of you
A product page
/products/:id with useParams and https://dummyjson.com/products/{id}.
Cards link to it.
Profast finishers
Filters in the URL
The category lives in ?category= with useSearchParams.
✓ F5 on /kitchen: the page opens. The sign let me in.
✗ GET /api/v1/enterprise/orders/queue 403 (Forbidden). The lock held.
Never put a secret in React code.
No admin passwords, no API keys, no "if (password === 'chef123')". Everything in the bundle is public.
Solution 2 Le Bistro, version 0.4
The house at the end of today
…/menu/5
…/login
…/forbidden
Full code: bistro-track/code/after-L30. Deploy it to Vercel: vercel.json is included.
Solution 2 The bugs of the second half
When auth goes wrong
Redirect without replace
<Navigate to="/login" />
Back from the login goes to /kitchen, which redirects to the login again: a loop.
<Navigate to="/login" replace />
Storing the password
localStorage.setItem("password", password)
Anyone with the laptop, or any script on the page, can read it.
Store the token. Never the password.
A secret in the frontend
if (code === "kitchen2026") setStaff(true)
The code is in the JavaScript bundle. It's public.
Roles come from the API: GET /auth/me
Forgetting the token
useApi("/api/v1/profile")
401 Unauthorized, even when signed in.
useApi("/api/v1/profile", session.token)
Quick check The doorman
A guest types /kitchen. Where do they land?
No session → the first check sends them to /login with from: "/kitchen". After a staff login they go back there. A signed-in student would get /forbidden instead.
Atelier · Classwork 2 · with AI 15 minutes, on your own project
Prompt it: a login and a guard
The taskthe AI writes, you check
BaseA /login page with DummyJSON (/auth/login, then /auth/me for the role); the session saved with useLocalStorage; the name in the Header.
PlusRequireAuth around /profile, and back to it after the login.
ProAn /admin page only for the admin role.
Your prompt, in five parts (CRAFT)
C
ContextYour shop, stack and react-router 8 ("import from react-router"). The two DummyJSON endpoints. Paste App.tsx and your layout.
R
Role"A React teacher who writes simple, safe code."
A
ActionThe Base task, then Plus in a follow-up prompt.
F
Format"Only the files that change, complete, with their paths."
T
Target"Tell me which part only hides a page and which part really protects it."
Done when
Log in, F5: still signed in
A guest on /profile lands on /login, then comes back
No password stored, imports from react-router
AI.md: your prompt and one thing you changed
A sample prompt · Fashion street
CONTEXT
My shop "Atelier" sells clothes. Vite + React 19 +
TypeScript + Tailwind 4, react-router 8 (import from
"react-router"). DummyJSON: POST /auth/login
{ username, password } -> accessToken;
GET /auth/me (Bearer) -> the user.
ROLE
A React teacher who writes simple, safe code.
ACTION
A /login page that signs in and saves { token, user }
with useLocalStorage, and shows the first name and
"Sign out" in the Header.
FORMAT
Only the files that change, complete, with their paths.
TARGET
Tell me which part only hides a page and which part
really protects it.
Masterclass · LAB-30 Press next: the prompt opens part by part
Le Bistro so far, in one prompt
Prompt · LAB-30 · Le Bistro after L30
C
Contextwhat exists and what we use
Le Bistro, a French restaurant site. Vite + React 19 + TypeScript + Tailwind CSS 4,
react-router 8 (import from "react-router"). API at /api through a Vite proxy:
dishes, categories, search; POST /api/v1/auth/login { email, password } ->
accessToken (lives 2 minutes); GET /api/v1/auth/me -> the role.
R
Rolewho answers
You are a senior React developer who writes code for beginners to read.
A
Actionwhat to build, step by step
Build the site as it is so far (real menu, saved cart, debounced search), now as pages:
- A layout with <Outlet />: /, /menu (?category= and ?q= in the URL), /menu/:id,
/login, /account, /kitchen, /forbidden and a 404.
- Login saves { token, user } with useLocalStorage; the Header shows the name.
- RequireAuth: guests go to /login and come back after it; /kitchen is only for
the kitchen and waiter roles. A 401 shows "Sign in again".
- vercel.json: /api to the API, every other address to index.html.
F
Formatwhat the answer looks like
The route table first, then every file in full with its path.
T
Targetwho reads it, when it's done
Beginners will read it: one component per file. The guard is a sign, not
a lock: no secrets in the code. It must pass npm run build.
Masterclass · LAB-30 The AI writes fast. You decide if it's right.
Run it, then check it
Copy the prompt into an AI toolClaude, ChatGPT, Copilot Chat: any of them; nothing to attach: the data comes from the API
Build what it gives backa new Vite project, every file pasted at its path
Runnpm run dev and npm run buildan error message goes straight back into the chat
Compare with code/after-L30the version we built by hand
Check it with what you learned today
Imports from react-router, not react-router-dom
<Link> inside the app, no <a href>
Number(id) before comparing a param
<Navigate … replace /> in the guard, state.from for the way back
The token is stored, never the password
Most AIs still answer with react-router-dom and version 6 code. One look at the imports tells you.
Masterclass · LAB-30 The same five parts, without the labels
The short version
Prompt · LAB-30 · the short version
You're a senior React developer writing for beginners.Le Bistro is a Vite + React 19 + TypeScript + Tailwind 4 app with react-router 8 (import from "react-router") and an API at /api through a Vite proxy: dishes, categories, search, POST /api/v1/auth/login (a 2-minute token) and GET /api/v1/auth/me for the role.Build the real menu, the saved cart and the debounced search as pages in a layout with <Outlet />: home, /menu with its filters in the URL, /menu/:id, login, /account, /kitchen for the kitchen and waiter roles only, /forbidden and a 404; RequireAuth sends guests to /login and back, a 401 shows "Sign in again", and vercel.json rewrites /api and every other address.Give the route table first, then every file in full with its path.Keep one component per file and no secrets in the code, and make sure npm run build passes.
ContextRoleActionFormatTarget
How CRAFT works
The five parts are a checklist for your thinking, not a form to fill in. Once all five are there, the labels can go: the prompt reads like a message to a colleague.
Dessert What we solved today
Two problems, two tools
Problem 1
One address for everything: no link to a dish, Back left the site.
React Router
Routes map addresses to pages. A layout frames them with <Outlet />. Link, params, search params.
Problem 2
Anyone could type /kitchen or /account.
Login and guards
A token from the API, a session that survives F5, RequireAuth with roles. The API is the lock.
Dessert Click a card to check yourself
What does this do?
01
01
Shows a page for /menu/anything, with the segment as a param.
02
02
The place in a layout where the current child page renders.
03
03
Changes the address without reloading the page. State is kept.
04
04
Reads the :id part of the address. Always a string.
05
05
Redirects while rendering, replacing the current history entry.
06
06
Shows the API our token on a protected request.
Homework Before L31
Your shop, version 0.4
Live link required
Baserequired
Pages and a login
Routes: /, /products, /products/:id, /login, /profile, *, in a layout with NavLinks.
Login with DummyJSON, the session saved, Sign out.
/profile locked with RequireAuth, back to it after the login.
vercel.json rewrite: F5 works on every page.
Plusrecommended
State in the URL
Category and search in ?category= and ?q=.
An admin-only page.
Before you submit
Send a product link: it opens
F5 on Vercel: no 404
No password stored anywhere
react-router, not react-router-dom
Full brief and grading: homework.md.
L30 La maison
Merci!
Questions now, or in the class chat. See you at L31.
Next time · L31
Count the props in App.tsx.
cart, onAdd, onChangeQty, session, onLogout… passed through layouts and pages that never use them. Next lecture: one cart and one session, available everywhere.