Ajout d’un layout global responsive avec React Router 7

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.

5 min read
Déverrouillez votre potentiel

avec React Router 7

Vous en avez marre de...

❌ perdre du temps à chercher des informations éparpillées
❌ ne pas avoir de retour sur votre progression
Assistant IA spécialisé

Posez vos questions 24/7 à notre IA experte en React Router 7

Quiz interactifs

Validez vos acquis avec des quiz personnalisés et un feedback instantané

9 modules
72 leçons
Accès à vie
299.49
-35%

Pourquoi ajouter un layout maintenant ?

Ton catalogue s’affiche, mais l’app manque encore des éléments « fixes » que l’on retrouve sur toutes les pages :

  1. une barre de navigation responsive ;
  2. un pied de page (meta, liens légaux, copyright) ;
  3. une page 404 cohérente avec la charte graphique.

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


1 Création de la barre de navigation

app/components/layout/navbar.tsx
1
import { useState } from "react";
2
import { Link } from "react-router";
3
import { Button } from "../ui/button";
4
5
export function Navbar() {
6
const [open, setOpen] = useState(false);
7
return (
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>
15
16
{/* 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">À&nbsp;propos</NavLink>
21
<NavLink to="/contact">Contact</NavLink>
22
</div>
23
24
{/* actions */}
25
<div className="flex items-center gap-4">
26
{/* panier */}
27
<button
28
aria-label="Panier"
29
className="p-2 text-gray-600 hover:text-black"
30
>
31
<CartIcon />
32
</button>
33
34
{/* 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>
39
40
{/* burger mobile */}
41
<button
42
aria-label="Menu"
43
onClick={() => setOpen(!open)}
44
className="md:hidden p-2 text-gray-600 hover:text-black"
45
>
46
{open ? <CloseIcon /> : <BurgerIcon />}
47
</button>
48
</div>
49
</div>
50
51
{/* 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}>À&nbsp;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">
63
Connexion
64
</Button>
65
<Button size="lg" className="w-full">
66
S'inscrire
67
</Button>
68
</div>
69
</div>
70
</div>
71
)}
72
</nav>
73
);
74
}
75
76
/* helpers */
77
78
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
79
return (
80
<Link
81
to={to}
82
className="font-light text-gray-600 transition-colors hover:text-black"
83
>
84
{children}
85
</Link>
86
);
87
}

Pourquoi ?

  • un seul composant gère desktop et mobile ;
  • le menu mobile couvre l’écran (fixed inset-0) pour garder la charte minimaliste ;
  • tous les chemins sont centralisés, donc faciles à mettre à jour.

app/components/layout/footer.tsx
1
import { Link } from "react-router";
2
3
export function Footer() {
4
return (
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">
13
L'avenir du travail avec nos robots humanoïdes.
14
</p>
15
<a
16
href="mailto:contact@algomax.fr"
17
className="font-light text-gray-600 hover:text-black"
18
>
19
contact@algomax.fr
20
</a>
21
</div>
22
23
{/* navigation générique */}
24
<FooterColumn title="Navigation">
25
<FooterLink to="/">Accueil</FooterLink>
26
<FooterLink to="/products">Produits</FooterLink>
27
<FooterLink to="/about">À&nbsp;propos</FooterLink>
28
<FooterLink to="/contact">Contact</FooterLink>
29
<FooterLink to="/blog">Blog</FooterLink>
30
</FooterColumn>
31
32
{/* modèles */}
33
<FooterColumn title="Nos&nbsp;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>
38
39
{/* 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">
44
Conditions d'utilisation
45
</FooterLink>
46
<FooterLink to="/politique-confidentialite">
47
Politique de confidentialité
48
</FooterLink>
49
</FooterColumn>
50
</div>
51
52
{/* baseline */}
53
<p className="border-t pt-8 text-center text-sm text-gray-500">
54
© {new Date().getFullYear()} NEXUS – Propulsé par&nbsp;
55
<a
56
href="https://algomax.fr"
57
className="text-gray-600 hover:text-black"
58
>
59
algomax.fr
60
</a>
61
</p>
62
</div>
63
</footer>
64
);
65
}
66
67
/* helpers */
68
69
function FooterColumn({
70
title,
71
children,
72
}: {
73
title: string;
74
children: React.ReactNode;
75
}) {
76
return (
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
}
83
84
function FooterLink({ to, children }: { to: string; children: React.ReactNode }) {
85
return (
86
<Link
87
to={to}
88
className="block font-light text-gray-600 transition-colors hover:text-black"
89
>
90
{children}
91
</Link>
92
);
93
}

Pourquoi ?

  • un composant FooterColumn évite de répéter structure + styles ;
  • dates et URLs sont dynamiques → pas d’oubli lors du passage à 2026.

3 Une page 404 cohérente

app/components/error-boundary.tsx {73-96}
1
if (isResponse && error.status === 404) {
2
return <NotFoundPage /> // page dédiée
3
}

La page NotFoundPage reprend la typographie, les couleurs et propose deux CTA : retour en arrière ou retour à l’accueil.

Bénéfices :

  • l’utilisateur n’est jamais perdu ;
  • Google voit un contenu 404 personnalisé (meilleure UX donc meilleur SEO).

4 Brancher le tout dans root.tsx

app/root.tsx {27-41}
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 :

  • la classe 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.

5 Organisation des fichiers

1
app/
2
├── components/
3
│ ├── layout/
4
│ │ ├── navbar.tsx
5
│ │ └── footer.tsx
6
│ └── error-boundary.tsx
7
└── root.tsx

Pourquoi tout en minuscules ?

  • Conventions plus simples à taper ;
  • Pas de mix PascalCase/kebabCase ;
  • Recherche **/layout/*.tsx → on trouve immédiatement navbar + footer.

6 Checklist rapide

✔︎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.

Conclusion

Tu disposes maintenant d’un layout global stylé et responsive :

  1. Navbar fixe, mobile-friendly, prête à accueillir le composant panier ;
  2. Footer complet avec liens légaux et brand ;
  3. Page 404 qui respecte la charte graphique.

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.

Premium
Quiz interactif
Testez vos connaissances et validez votre compréhension du module avec notre quiz interactif.
1

Comprendre les concepts fondamentaux

Quelle est la principale différence entre les composants client et serveur dans React ?

Les composants client s'exécutent uniquement dans le navigateur
Les composants serveur peuvent utiliser useState
Les composants client sont plus rapides
Il n'y a aucune différence significative
2

Optimisation des performances

Quelle technique est recommandée pour éviter les rendus inutiles dans React ?

Utiliser React.memo pour les composants fonctionnels
Ajouter plus d'états locaux
Éviter d'utiliser les props
Toujours utiliser les class components
3

Architecture des données

Quel hook permet de gérer les effets de bord dans un composant React ?

useEffect
useState
useMemo
useContext
4

Gestion des erreurs

Comment implémenter la gestion des erreurs pour les requêtes API dans React ?

Utiliser try/catch avec async/await
Ignorer les erreurs
Toujours afficher un message d'erreur
Rediriger l'utilisateur
5

Déploiement et CI/CD

Quelle est la meilleure pratique pour déployer une application React en production ?

Utiliser un service CI/CD comme GitHub Actions
Copier les fichiers manuellement via FTP
Envoyer le code source complet
Ne jamais mettre à jour l'application

Débloquez ce quiz et tous les autres contenus premium en achetant ce cours