Apprends à créer une navbar, un footer et une page 404 cohérente pour ton projet React Router 7. Structure solide, code factorisé, expérience utilisateur optimale.
avec React Router 7
Posez vos questions 24/7 à notre IA experte en React Router 7
Validez vos acquis avec des quiz personnalisés et un feedback instantané
Ton catalogue s’affiche, mais l’app manque encore des éléments « fixes » que l’on retrouve sur toutes les pages :
Objectif de cette leçon : construire ces trois briques, les brancher dans
root.tsx
, puis factoriser le code pour préparer les prochaines pages
(produits, panier, blog...).
1import { useState } from "react";2import { Link } from "react-router";3import { Button } from "../ui/button";45export function Navbar() {6const [open, setOpen] = useState(false);7return (8<nav className="bg-white border-b border-gray-200 sticky top-0 z-50">9<div className="container mx-auto flex h-16 items-center justify-between px-6">10{/* logo */}11<Link to="/" className="flex items-center gap-2">12<div className="h-8 w-8 rounded-lg bg-gradient-to-r from-blue-600 to-purple-600" />13<span className="text-xl font-light">NEXUS</span>14</Link>1516{/* liens desktop */}17<div className="hidden md:flex items-center gap-8">18<NavLink to="/">Accueil</NavLink>19<NavLink to="/products">Produits</NavLink>20<NavLink to="/about">À propos</NavLink>21<NavLink to="/contact">Contact</NavLink>22</div>2324{/* actions */}25<div className="flex items-center gap-4">26{/* panier */}27<button28aria-label="Panier"29className="p-2 text-gray-600 hover:text-black"30>31<CartIcon />32</button>3334{/* auth desktop */}35<div className="hidden md:flex items-center gap-3">36<Button variant="ghost" size="sm">Connexion</Button>37<Button size="sm">S'inscrire</Button>38</div>3940{/* burger mobile */}41<button42aria-label="Menu"43onClick={() => setOpen(!open)}44className="md:hidden p-2 text-gray-600 hover:text-black"45>46{open ? <CloseIcon /> : <BurgerIcon />}47</button>48</div>49</div>5051{/* menu mobile */}52{open && (53<div className="md:hidden fixed inset-0 top-16 bg-white">54<div className="flex h-full flex-col justify-between px-6 py-8">55<div className="flex-1 space-y-8">56<MobileLink to="/" setOpen={setOpen}>Accueil</MobileLink>57<MobileLink to="/products" setOpen={setOpen}>Produits</MobileLink>58<MobileLink to="/about" setOpen={setOpen}>À propos</MobileLink>59<MobileLink to="/contact" setOpen={setOpen}>Contact</MobileLink>60</div>61<div className="space-y-4 border-t pt-8">62<Button variant="ghost" size="lg" className="w-full">63Connexion64</Button>65<Button size="lg" className="w-full">66S'inscrire67</Button>68</div>69</div>70</div>71)}72</nav>73);74}7576/* helpers */7778function NavLink({ to, children }: { to: string; children: React.ReactNode }) {79return (80<Link81to={to}82className="font-light text-gray-600 transition-colors hover:text-black"83>84{children}85</Link>86);87}
Pourquoi ?
fixed inset-0
) pour garder la charte
minimaliste ;1import { Link } from "react-router";23export function Footer() {4return (5<footer className="mt-auto border-t bg-white">6<div className="container mx-auto space-y-12 px-6 py-12">7{/* 4 colonnes : brand, nav, modèles, légal */}8<div className="grid gap-8 md:grid-cols-4">9{/* brand */}10<div className="space-y-4">11<Logo />12<p className="font-light text-gray-500">13L'avenir du travail avec nos robots humanoïdes.14</p>15<a16href="mailto:contact@algomax.fr"17className="font-light text-gray-600 hover:text-black"18>19contact@algomax.fr20</a>21</div>2223{/* navigation générique */}24<FooterColumn title="Navigation">25<FooterLink to="/">Accueil</FooterLink>26<FooterLink to="/products">Produits</FooterLink>27<FooterLink to="/about">À propos</FooterLink>28<FooterLink to="/contact">Contact</FooterLink>29<FooterLink to="/blog">Blog</FooterLink>30</FooterColumn>3132{/* modèles */}33<FooterColumn title="Nos modèles">34<FooterLink to="/products/nexus-01">NEXUS-01</FooterLink>35<FooterLink to="/products/atlas-02">ATLAS-02</FooterLink>36<FooterLink to="/products/titan-03">TITAN-03</FooterLink>37</FooterColumn>3839{/* légal */}40<FooterColumn title="Informations">41<FooterLink to="/mentions-legales">Mentions légales</FooterLink>42<FooterLink to="/cgv">CGV</FooterLink>43<FooterLink to="/conditions-utilisation">44Conditions d'utilisation45</FooterLink>46<FooterLink to="/politique-confidentialite">47Politique de confidentialité48</FooterLink>49</FooterColumn>50</div>5152{/* baseline */}53<p className="border-t pt-8 text-center text-sm text-gray-500">54© {new Date().getFullYear()} NEXUS – Propulsé par 55<a56href="https://algomax.fr"57className="text-gray-600 hover:text-black"58>59algomax.fr60</a>61</p>62</div>63</footer>64);65}6667/* helpers */6869function FooterColumn({70title,71children,72}: {73title: string;74children: React.ReactNode;75}) {76return (77<div className="space-y-4">78<h3 className="text-lg font-light">{title}</h3>79<div className="space-y-3">{children}</div>80</div>81);82}8384function FooterLink({ to, children }: { to: string; children: React.ReactNode }) {85return (86<Link87to={to}88className="block font-light text-gray-600 transition-colors hover:text-black"89>90{children}91</Link>92);93}
Pourquoi ?
FooterColumn
évite de répéter structure + styles ;1if (isResponse && error.status === 404) {2return <NotFoundPage /> // page dédiée3}
La page NotFoundPage
reprend la typographie, les couleurs et propose
deux CTA : retour en arrière ou retour à l’accueil.
Bénéfices :
root.tsx
1<body className="flex min-h-screen flex-col">2<Navbar />3<div className="flex-1">4{children}5</div>6<Footer />7<ScrollRestoration />8<Scripts />9</body>
Explications :
flex min-h-screen flex-col
permet au footer d’être collé en bas
même sur les pages courtes ;Navbar
+ Footer
sont importés depuis components/layout
→ faciles à
modifier sans toucher au root
.1app/2├── components/3│ ├── layout/4│ │ ├── navbar.tsx5│ │ └── footer.tsx6│ └── error-boundary.tsx7└── root.tsx
Pourquoi tout en minuscules ?
**/layout/*.tsx
→ on trouve immédiatement navbar + footer.✔︎ | Point à vérifier |
---|---|
Le menu mobile se ferme après un clic (onClick → setOpen(false) ). | |
Le footer s’affiche sous le contenu même quand la page est courte. | |
Pas de défilement quand le menu mobile est ouvert (fixed inset-0 ). | |
La page 404 utilise bien la nouvelle NotFoundPage . | |
Les composants sont rangés dans components/layout et importés dans root.tsx . |
Tu disposes maintenant d’un layout global stylé et responsive :
Ces trois briques seront visibles sur toutes les routes : c’est la base sur laquelle on va pouvoir construire les pages dynamiques (produit, panier, blog…) sans réécrire le décor à chaque fois.
Quelle est la principale différence entre les composants client et serveur dans React ?
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Quel hook permet de gérer les effets de bord dans un composant React ?
Comment implémenter la gestion des erreurs pour les requêtes API dans React ?
Quelle est la meilleure pratique pour déployer une application React en production ?